domma-cms 0.104.2 → 0.105.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{path as s,ellipse as d,gradient as h,rect as b}from"./christmas-drawing.js";function C(o,a,v=!1){return![a.x,a.y,a.size].every(Number.isFinite)||a.size<=0?!1:(o.save(),o.translate(a.x,a.y),o.scale(a.size*(v&&a.vx<0?-1:1),a.size),o.rotate(a.rotation||0),o.globalAlpha*=a.opacity??1,o.lineCap="round",o.lineJoin="round",!0)}function m(o,a,v,e){const i=[];for(let r=0;r<10;r++){const n=-Math.PI/2+r*Math.PI/5,f=e*(r%2?.44:1);i.push([r?"lineTo":"moveTo",a+Math.cos(n)*f,v+Math.sin(n)*f])}i.push(["closePath"]),s(o,i,"#f4d68b","#bb9655",.02)}export const TREE_GIFTS=[{x:-.78,y:.94,width:.48,height:.38,color:"#ad3448"},{x:.36,y:1.04,width:.5,height:.3,color:"#39766e"},{x:-.16,y:1.04,width:.37,height:.29,color:"#b58948"}];export function drawGift(o,a,v=a.x,e=a.y){b(o,v,e,a.width,a.height,.025,a.color,"#d8bc82",.015),b(o,v-.015,e,a.width+.03,.07,.012,a.color,"#d8bc82",.015),s(o,[["moveTo",v+a.width/2,e],["lineTo",v+a.width/2,e+a.height]],null,"#f2dca6",.055);const i=v+a.width/2;s(o,[["moveTo",i,e],["bezierCurveTo",i-.25,e-.21,i-.24,e+.02,i,e],["bezierCurveTo",i+.25,e-.21,i+.24,e+.02,i,e]],null,"#f2dca6",.025)}export function drawTree(o,a,v=0){if(C(o,a)){b(o,-.13,.64,.26,.65,.04,h(o,.6,1.3,["#a58154","#674933"]));for(let e=3;e>=0;e--){const i=-1.38+e*.47,r=.38+e*.24,n=i+.88;s(o,[["moveTo",0,i],["quadraticCurveTo",-r*.33,n-.2,-r,n],["quadraticCurveTo",-r*.66,n+.11,-r*.45,n+.04],["quadraticCurveTo",0,n+.22,r*.45,n+.04],["quadraticCurveTo",r*.66,n+.11,r,n],["quadraticCurveTo",r*.33,n-.2,0,i],["closePath"]],h(o,i,n+.2,["#5c8b72","#2d5c48","#173d35"]));for(let l of[-1,1])for(let u=1;u<=3;u++){const T=i+.28+u*.14,t=l*r*u/5;s(o,[["moveTo",l*.04,T-.09],["quadraticCurveTo",t*.7,T+.07,t,T+.04]],null,"#6a9875",.016)}const f=n-.12;s(o,[["moveTo",-r*.7,f],["quadraticCurveTo",0,f+.29,r*.7,f]],null,"#c9aa6d",.024);for(let l=0;l<5;l++){const u=l/4,T=(u*2-1)*r*.7,t=f+.58*u*(1-u);o.save(),o.globalAlpha*=.75+Math.sin(v*.002+l+e)*.2,o.shadowColor="#ffdd8e",o.shadowBlur=3,d(o,T,t,.035,.035,"#ffe4a4"),o.restore()}for(const l of[-1,1]){const u=l*r*.38,T=n-.01;d(o,u,T,.07,.085,l<0?"#c55459":"#d5b16d"),d(o,u-.025,T-.03,.02,.025,"#f4d9b0")}}o.save(),o.shadowColor="#eacd8e",o.shadowBlur=6,m(o,0,-1.47,.2),o.restore();for(const e of a.gifts||TREE_GIFTS)e.stolen||drawGift(o,e);o.restore()}}export function drawWreath(o,a,v=0){if(C(o,a)){o.beginPath(),o.arc(0,0,.85,0,Math.PI*2),o.strokeStyle="#1c3e33",o.lineWidth=.38,o.stroke();for(let e=0;e<28;e++){const i=e*Math.PI*2/28;o.save(),o.rotate(i),o.translate(.85,0),o.rotate(.45),s(o,[["moveTo",-.2,0],["quadraticCurveTo",0,-.06,.27,-.02]],null,"#86a580",.016);for(let r=0;r<3;r++){const n=-.16+r*.11;s(o,[["moveTo",n,0],["quadraticCurveTo",n-.11,-.17,n+.13,-.14],["quadraticCurveTo",n+.12,-.04,n,0],["moveTo",n,0],["quadraticCurveTo",n-.11,.18,n+.13,.14],["quadraticCurveTo",n+.12,.04,n,0]],e%3?"#3c7053":"#61916c")}o.restore()}for(const e of[-.5,.35,2,3.3,4.6]){const i=Math.cos(e)*.86,r=Math.sin(e)*.86;for(const[n,f]of[[-.045,-.035],[.05,-.015],[0,.055]])d(o,i+n,r+f,.055,.055,"#b63d4a"),d(o,i+n-.016,r+f-.017,.014,.014,"#efac93")}for(let e=0;e<7;e++){const i=e*Math.PI*2/7,r=Math.cos(i)*.91,n=Math.sin(i)*.91;o.save(),o.globalAlpha*=.7+Math.sin(v*.002+e)*.2,o.shadowColor="#ffe3a1",o.shadowBlur=3,d(o,r,n,.028,.028,"#ffe3a1"),o.restore()}s(o,[["moveTo",-.08,.68],["lineTo",-.28,1.34],["lineTo",-.05,1.24],["lineTo",.08,1.37],["lineTo",.16,.76],["closePath"]],"#9c293e"),s(o,[["moveTo",.06,.74],["lineTo",.23,1.3],["lineTo",.35,1.18],["lineTo",.55,1.29],["lineTo",.31,.73],["closePath"]],"#c14653");for(const e of[-1,1])o.save(),o.scale(e,1),s(o,[["moveTo",0,.78],["bezierCurveTo",.6,.28,.76,.95,.39,1.02],["quadraticCurveTo",.19,1.01,0,.78],["closePath"]],h(o,.45,1.05,["#e16b70","#a32d43"])),s(o,[["moveTo",.09,.79],["quadraticCurveTo",.38,.6,.48,.73]],null,"#f4a79b",.025),o.restore();d(o,.03,.8,.13,.12,"#c64858"),o.restore()}}export function drawRobin(o,a,v=0){if(!C(o,a,!0))return;const e=a.state!=="sitting",i=v*.014+(a.waveOffset||0);s(o,[["moveTo",-.4,.14],["lineTo",-1.15,.05],["lineTo",-1.02,.35],["lineTo",-.4,.44],["closePath"]],"#715f50"),d(o,-.03,.04,.66,.71,h(o,-.6,.75,["#a48b6e","#ddd2bc"])),d(o,.3,-.4,.46,.44,"#a18b6d"),d(o,.38,-.02,.39,.49,h(o,-.5,.5,["#e99050","#be5839"])),o.save(),o.translate(-.24,-.15),o.rotate(e?-.4+Math.sin(i)*.85:-.12),s(o,[["moveTo",.1,0],["bezierCurveTo",-.27,-.39,-.96,-.21,-.68,.17],["quadraticCurveTo",-.26,.54,.1,0],["closePath"]],h(o,-.35,.38,["#b69c78","#796449"]));for(let r=0;r<3;r++)s(o,[["moveTo",-.15-r*.14,-.07],["quadraticCurveTo",-.39-r*.12,.16,-.58-r*.05,.12]],null,"#d1b58b",.025);o.restore(),s(o,[["moveTo",.68,-.38],["lineTo",.91,-.29],["lineTo",.68,-.24],["closePath"]],"#41352e"),d(o,.49,-.48,.07,.077,"#24252b"),d(o,.51,-.5,.022,.022,"#fff2db");for(const r of[-.12,.19])s(o,[["moveTo",r,.62],["lineTo",r+(e?-.14:.02),e?.76:.97],["lineTo",r+.17,e?.77:.97]],null,"#957155",.045);s(o,[["moveTo",-.1,-.72],["quadraticCurveTo",.12,-1.32,.56,-1.09],["quadraticCurveTo",.75,-.99,.77,-.79],["quadraticCurveTo",.46,-1.03,.55,-.71],["closePath"]],h(o,-1.25,-.7,["#d75b61","#aa3147"])),s(o,[["moveTo",-.12,-.72],["quadraticCurveTo",.22,-.83,.56,-.71]],null,"#f4e9d0",.1),d(o,.77,-.8,.09,.09,"#f4e9d0"),o.restore()}export function drawElf(o,a){if(!C(o,a,!0))return;const v=a.time||0,e=a.type==="peeing-elf"&&a.state==="peeing",i=a.type==="thieving-elf"&&a.state==="escaping",n=a.type==="elf"||a.state==="approaching"||a.state==="escaping"?Math.sin(v*(i?.019:.009))*.3:0;if(e)for(let l=0;l<15;l++){const u=(v*7e-4+l/15)%1;d(o,.26+u*1.12,.3-u*.65+u*u*1.16,.015,.022,"#dfc477")}for(const[l,u]of[[0,-.16],[1,.14]]){const T=n*(l?-1:1);s(o,[["moveTo",u,.21],["lineTo",u+T,.53],["lineTo",u+T*1.5,.88]],null,l?"#ebe2c4":"#b8ae91",.13);for(let t=0;t<3;t++)s(o,[["moveTo",u+T*(1+t/6)-.06,.57+t*.1],["lineTo",u+T*(1+t/6)+.06,.57+t*.1]],null,"#b8444a",.045);s(o,[["moveTo",u+T*1.5-.09,.85],["quadraticCurveTo",u+T*1.5-.18,1.05,u+T*1.5+.17,1],["quadraticCurveTo",u+T*1.5+.44,.98,u+T*1.5+.33,.82],["quadraticCurveTo",u+T*1.5+.22,.94,u+T*1.5+.12,.84]],"#543c36")}s(o,[["moveTo",-.22,-.47],["quadraticCurveTo",-.44,-.19,-.4,.37],["lineTo",-.18,.3],["lineTo",-.07,.42],["lineTo",.07,.3],["lineTo",.3,.38],["quadraticCurveTo",.38,-.2,.2,-.47],["closePath"]],h(o,-.5,.4,["#648e67","#315c43"])),s(o,[["moveTo",-.37,.14],["lineTo",.32,.14]],null,"#533b32",.09),b(o,-.045,.07,.13,.13,.018,"#d9b870","#ffe5a2",.018),s(o,[["moveTo",-.2,-.49],["lineTo",-.23,-.26],["lineTo",0,-.4],["lineTo",.18,-.24],["lineTo",.2,-.49]],"#b5454e");const f=e?.2:i?-.02:.22;s(o,[["moveTo",.19,-.35],["quadraticCurveTo",.34,-.1,.41,f]],null,"#4a7654",.14),d(o,.43,f,.085,.075,"#edc296"),i&&drawGift(o,{width:.52,height:.39,color:a.gift?.color||"#b74450"},.3,-.15),d(o,0,-.69,.28,.3,e?"#967349":"#efc89f");for(const l of[-1,1])s(o,[["moveTo",l*.22,-.81],["lineTo",l*.43,-.9],["quadraticCurveTo",l*.42,-.62,l*.21,-.65]],"#e2b38a");e||(d(o,.13,-.75,.024,.035,"#322e30"),d(o,.29,-.65,.07,.055,"#dc9e7c"),s(o,[["moveTo",.08,-.57],["quadraticCurveTo",.17,-.49,.22,-.58]],null,"#956349",.025)),s(o,[["moveTo",-.29,-.91],["quadraticCurveTo",-.21,-1.38,.21,-1.3],["quadraticCurveTo",.48,-1.29,.5,-1.06],["quadraticCurveTo",.25,-1.2,.28,-.9],["closePath"]],h(o,-1.38,-.9,["#d56862","#a33747"])),s(o,[["moveTo",-.29,-.9],["quadraticCurveTo",0,-.97,.28,-.9]],null,"#d9b878",.065),d(o,.5,-1.05,.06,.075,"#f1ce7e"),o.restore()}
@@ -0,0 +1 @@
1
+ import{path as l,ellipse as i,gradient as m,rect as T}from"./christmas-drawing.js";export function drawSleigh(o,e){const b=Math.abs(e.targetX-e.startX),d=b?Math.min(1,Math.abs(e.x-e.startX)/b):0,h=e.baseY-Math.sin(d*Math.PI)*e.arcHeight,u=e.size*1.5;if(![e.x,h,u].every(Number.isFinite)||u<=0)return;const v=e.time||0;o.save(),o.translate(e.x,h),o.scale((e.vx>=0?1:-1)*u,u),o.globalAlpha*=e.opacity??1,o.lineCap="round",o.lineJoin="round",l(o,[["moveTo",.65,-.48],["bezierCurveTo",2,-.1,4,-.2,6.7,-.35]],null,"#d6b675",.022),[[3.1,-.46],[4.65,-.46],[2.65,.18],[4.2,.18],[6.05,-.12]].forEach(([s,C],n)=>{const a=v*.008+n*.85;o.save(),o.translate(s,C+Math.sin(a)*.035);for(let r=0;r<4;r++){const f=r<2?-.27:.28,g=Math.sin(a+r*1.7)*.2;l(o,[["moveTo",f,.02],["lineTo",f+g,.26],["lineTo",f+g-.14,.43]],null,r%2?"#b88960":"#674832",.055),l(o,[["moveTo",f+g-.17,.43],["lineTo",f+g-.08,.43]],null,"#302a29",.055)}i(o,0,-.08,.46,.2,m(o,-.3,.12,["#cfa274","#98623d"])),l(o,[["moveTo",.19,-.1],["quadraticCurveTo",.34,-.34,.33,-.55],["lineTo",.5,-.62],["lineTo",.7,-.49],["quadraticCurveTo",.76,-.38,.59,-.37],["lineTo",.47,-.4],["lineTo",.43,-.02],["closePath"]],"#b58356"),l(o,[["moveTo",.39,-.55],["lineTo",.26,-.73],["quadraticCurveTo",.48,-.73,.49,-.56]],"#cfaa80"),l(o,[["moveTo",-.4,-.09],["lineTo",-.57,-.2]],null,"#cba178",.07),l(o,[["moveTo",.38,-.61],["lineTo",.28,-.83],["lineTo",.3,-1.01],["moveTo",.29,-.82],["lineTo",.13,-.91],["lineTo",.11,-1.02],["moveTo",.28,-.9],["lineTo",.4,-.98]],null,"#dfc59a",.03),i(o,.52,-.51,.018,.025,"#201e22"),l(o,[["moveTo",-.11,-.25],["lineTo",.03,.1],["moveTo",.39,-.3],["lineTo",.48,-.24]],null,"#9a2630",.055),i(o,.07,.03,.035,.035,"#f2cf7c"),n===4?(o.save(),o.shadowColor="#ff493b",o.shadowBlur=7,i(o,.71,-.44,.045,.04,"#ff5c45"),o.restore()):i(o,.71,-.44,.025,.03,"#35272a"),o.restore()}),i(o,-.4,-.49,.47,.55,m(o,-1,0,["#806846","#483824"])),T(o,-.72,-.96,.33,.29,.035,"#247260"),T(o,-.43,-1.13,.32,.37,.035,"#a73345"),l(o,[["moveTo",-.55,-.96],["lineTo",-.55,-.67],["moveTo",-.27,-1.13],["lineTo",-.27,-.76]],null,"#e6c681",.035),i(o,.35,-.48,.3,.39,"#b92e40"),i(o,.42,-.93,.19,.21,"#edbf9a"),l(o,[["moveTo",.22,-.95],["quadraticCurveTo",.24,-.58,.42,-.57],["quadraticCurveTo",.68,-.75,.6,-.94],["quadraticCurveTo",.4,-.79,.22,-.95]],"#fff1d9"),l(o,[["moveTo",.2,-1.09],["quadraticCurveTo",.36,-1.52,.68,-1.17],["lineTo",.72,-1.1],["quadraticCurveTo",.46,-1.3,.58,-1.08]],"#c73a46"),l(o,[["moveTo",.21,-1.08],["lineTo",.6,-1.08]],null,"#fff4de",.09),i(o,.71,-1.12,.07,.07,"#fff4de"),l(o,[["moveTo",.45,-.53],["quadraticCurveTo",.63,-.36,.79,-.49]],null,"#d7434b",.13),i(o,.8,-.48,.08,.055,"#f2ddbd"),l(o,[["moveTo",-.94,-.64],["quadraticCurveTo",-.83,.3,-.35,.32],["lineTo",.86,.32],["quadraticCurveTo",1.34,.26,1.4,-.62],["quadraticCurveTo",1.11,-.19,.81,-.12],["lineTo",-.36,-.12],["quadraticCurveTo",-.64,-.17,-.94,-.64],["closePath"]],m(o,-.65,.35,["#ed5b5b","#ae2038","#631a30"]),"#e7c789",.04),l(o,[["moveTo",-.48,0],["quadraticCurveTo",-.38,.2,-.16,.21],["lineTo",.8,.21],["quadraticCurveTo",1,.16,1.06,-.02]],null,"#d59959",.025);for(const s of[0,.1])l(o,[["moveTo",-.48,.32],["lineTo",-.6,.52+s],["moveTo",.77,.32],["lineTo",.87,.52+s]],null,"#b99559",.045),l(o,[["moveTo",-1.04,.5+s],["quadraticCurveTo",.4,.72+s,1.31,.49+s],["bezierCurveTo",1.76,.36+s,1.64,.03+s,1.43,.17+s]],null,"#f0ce88",.045);o.restore()}export function drawTrain(o,e){if(![e.x,e.y,e.size].every(Number.isFinite)||e.size<=0)return;o.save(),o.globalAlpha*=e.opacity??1;for(const n of e.smoke||[]){o.save(),o.globalAlpha*=Math.max(0,n.opacity);const a=o.createRadialGradient(n.x,n.y,0,n.x,n.y,n.size);a.addColorStop(0,"rgba(237,231,216,0.55)"),a.addColorStop(.55,"rgba(213,218,224,0.3)"),a.addColorStop(1,"rgba(213,218,224,0)"),i(o,n.x,n.y,n.size,n.size,a),o.restore()}o.translate(e.x,e.y),o.scale((e.vx>=0?1:-1)*e.size*1.8/20,e.size*1.8/20),o.lineCap="round",o.lineJoin="round";const b=(e.time||0)*.005,d="#d9b779",h=m(o,-42,-12,["#42766b","#204c45","#12312f"]),u=m(o,-36,-16,["#56626b","#232e36","#101b23"]);function v(n,a){o.save(),o.translate(n,-a),i(o,0,0,a,a,"#101820"),o.beginPath(),o.arc(0,0,a-.5,0,Math.PI*2),o.strokeStyle="#84908d",o.lineWidth=.7,o.stroke(),o.rotate(b);for(let r=0;r<8;r++){const f=r*Math.PI/4;l(o,[["moveTo",0,0],["lineTo",Math.cos(f)*(a-1.4),Math.sin(f)*(a-1.4)]],null,"#a05a4b",.8)}i(o,0,0,1.4,1.4,d),o.restore()}const q=Math.max(0,Math.min(4,e.carriages??3));for(let n=q;n>=1;n--){const a=-25-n*75+15;l(o,[["moveTo",a+60,-10],["lineTo",a+75,-10]],null,"#a1aaa5",1.3),v(a+13,5),v(a+47,5),T(o,a,-43,60,35,2,h,d),T(o,a-2,-47,64,5,2,u,"#78847f"),T(o,a-1,-12,62,4,1,"#17252a");for(let r=0;r<4;r++){const f=a+5+r*13;T(o,f,-38,10,17,1.5,m(o,-38,-21,["#fff1bd","#d7a759"]),d),l(o,[["moveTo",f+5,-38],["lineTo",f+5,-21]],null,"#746b4b",.6),r%2===0&&(i(o,f+3,-27,1.7,2,"#594b3d"),T(o,f+.8,-25,4.5,4,1,"#594b3d"))}l(o,[["moveTo",a+3,-17],["lineTo",a+57,-17]],null,d,.55);for(let r=0;r<9;r++)i(o,a+5+r*6,-41,.8,.8,r%2?"#efbc65":"#df6e60")}T(o,-29,-16,86,6,1,"#17232b","#64716f"),T(o,-25,-42,25,27,2,h,d),T(o,-29,-46,32,5,2,u,"#7e8980"),T(o,-20,-38,13,14,1.4,m(o,-38,-24,["#ffedb1","#cba565"]),d),l(o,[["moveTo",-13.5,-38],["lineTo",-13.5,-24]],null,"#53655d",.8),T(o,-2,-36,50,20,8,u,"#a0a394");for(const n of[5,22,39])T(o,n,-35,1.6,18,.3,d);i(o,48,-26,4,10,"#24353b"),l(o,[["moveTo",28,-35],["lineTo",27,-43],["lineTo",36,-43],["lineTo",35,-35],["closePath"]],u,"#929787",.7),T(o,26,-45,11,2,.8,"#26353c",d),i(o,12,-36,4,3,d),T(o,50,-28,5,5,1,"#e6c780"),o.save(),o.shadowColor="#ffe4a1",o.shadowBlur=8,i(o,54,-25.5,1.6,2,"#fff1c5"),o.restore(),l(o,[["moveTo",56,-15],["lineTo",64,-3],["lineTo",52,-3],["closePath"]],"#273841","#9eaa9f",.6);for(const n of[15,35])v(n,8);v(51,4),v(-15,5);const s=Math.cos(b)*4.6,C=Math.sin(b)*4.6;l(o,[["moveTo",15+s,-8+C],["lineTo",35+s,-8+C],["lineTo",45,-14]],null,"#d0d0b9",1.3);for(const n of[15,35])i(o,n+s,-8+C,1,1,"#f2d08c");o.restore()}
@@ -1 +1 @@
1
- import{createParticle as ee}from"./../core/particles.js";export default{name:"christmas",displayName:"Christmas",emoji:"\u{1F384}",intensityConfig:{light:{count:50,speedRange:[.5,1.5],sizeRange:[1,3],trees:3,wreaths:2,northStars:1,snowmen:2},medium:{count:150,speedRange:[.8,2.5],sizeRange:[1,4],trees:6,wreaths:3,northStars:1,snowmen:3},heavy:{count:300,speedRange:[1,3.5],sizeRange:[1,5],trees:10,wreaths:4,northStars:1,snowmen:4}},particles:["snowflake"],decorations:["tree","wreath","sleigh","robin","train","elf","firework","north-star","snowman"],decorationKeys:["trees","wreaths","northStars","snowmen","sleigh","elf","train","firework","robin"],colors:{primary:"#ffffff",secondary:"#228B22",accent:"#c00",gold:"#FFD700"},createSnowflakeParticle(e,i,s){const y=ee(s,e,i);return y.type="snowflake",y},createFallingParticle(e,i,s){return this.createSnowflakeParticle(e,i,s)},createTree(e,i,s={}){return{type:"tree",x:s.x!==void 0?s.x:Math.random()*e,y:s.y!==void 0?s.y:Math.random()*i,vx:0,vy:0,size:20+Math.random()*15,opacity:.6+Math.random()*.3,rotation:0,rotationSpeed:0,active:!0,static:!0}},createWreath(e,i,s={}){const y=[];for(let o=0;o<20;o++){const h=o/20*Math.PI*2;y.push({angle:h,radius:.9+Math.random()*.2,thickness:.2+Math.random()*.15,color:o%2===0?"#1a6b1a":"#228B22"})}return{type:"wreath",x:s.x!==void 0?s.x:Math.random()*e,y:s.y!==void 0?s.y:Math.random()*i,vx:0,vy:0,size:15+Math.random()*10,opacity:.7+Math.random()*.2,rotation:0,rotationSpeed:0,active:!0,static:!0,shape:y}},createNorthStar(e,i,s={}){return{type:"north-star",x:s.x!==void 0?s.x:e/2,y:s.y!==void 0?s.y:80,vx:0,vy:0,size:25,opacity:1,twinklePhase:Math.random()*Math.PI*2,twinkleSpeed:.003,active:!0,static:!0}},createSnowman(e,i,s={}){return{type:"snowman",x:s.x!==void 0?s.x:Math.random()*e,y:s.y!==void 0?s.y:i-50,vx:0,vy:0,size:15+Math.random()*10,opacity:1,time:Math.random()*1e3,wavePhase:Math.random()*Math.PI*2,active:!0,static:!0}},createInitialDecorations(e,i,s){const y=[],n=s.trees??6;for(let l=0;l<n;l++)y.push(this.createTree(e,i,{x:e/(n+1)*(l+1),y:i-60-Math.random()*20}));const o=s.wreaths??3;for(let l=0;l<o;l++)y.push(this.createWreath(e,i,{x:e/(o+1)*(l+1),y:50+Math.random()*100}));s.northStars!==0&&s.northStars&&y.push(this.createNorthStar(e,i,{x:e/2,y:60}));const h=s.snowmen??3;for(let l=0;l<h;l++)y.push(this.createSnowman(e,i,{x:e/(h+1)*(l+1),y:i-50-Math.random()*10}));return y},spawnSpecialParticle(e,i,s,y={}){const n=Math.random();if(n<5e-4){if(y.sleigh===0||e.some(a=>a.type==="sleigh"))return null;const o=Math.random()<.5,h=o?-100:i+100,l=100+Math.random()*(s*.3);return{type:"sleigh",x:h,startX:h,y:l,baseY:l,targetX:o?i+100:-100,canvasWidth:i,vx:o?3+Math.random()*2:-(3+Math.random()*2),vy:0,arcHeight:150+Math.random()*100,time:0,size:15+Math.random()*10,opacity:.9,rotation:0,active:!0,static:!1}}else if(n<.0013&&y.elf!==0){const o=Math.random()<.5;return{type:"elf",x:o?-50:i+50,y:s-30,baseY:s-30,vx:o?1.5+Math.random()*1:-(1.5+Math.random()*1),waveAmplitude:3,waveFrequency:.05,waveOffset:Math.random()*Math.PI*2,time:0,size:10+Math.random()*5,opacity:.95,rotation:0,active:!0,static:!1}}else if(n<.005&&y.train!==0){if(e.some(g=>g.type==="train"))return null;const o=Math.random()<.5,h=o?-500:i+500,l=21+Math.random()*9,a=l*.72,r=s-a-10;return{type:"train",x:h,y:r,baseY:r,vx:o?4+Math.random()*2:-(4+Math.random()*2),vy:0,size:l,opacity:1,time:0,smoke:[],active:!0,static:!1,carriages:2+Math.floor(Math.random()*2)}}else{if(n<.008&&y.firework!==0)return{type:"firework",x:Math.random()*i,y:s,vx:(Math.random()-.5)*4,vy:-10-Math.random()*5,size:2+Math.random()*2,opacity:1,active:!0,static:!1,time:0,exploded:!1,explosionTime:30+Math.random()*30};if(n<.012&&y.robin!==0){if(e.some(t=>t.type==="robin"))return null;const o=Math.random()<.5,h=Math.random()*(s*.2),l=o?-50:i+50,a=10+Math.random()*5,r=Math.max(a*3,Math.min(s*.6-a*2,s*.2+Math.random()*(s*.4))),g=Math.random()*(i*.6)+i*.2;return{type:"robin",state:"flying_in",x:l,y:h,startX:l,startY:h,targetX:g,targetY:r,vx:o?.5+Math.random()*.5:-(.5+Math.random()*.5),vy:0,size:a,opacity:.95,active:!0,static:!1,sitTime:3e3+Math.random()*2e3,sitStartTime:0,flightProgress:0,time:0,waveOffset:Math.random()*Math.PI*2}}}return null},updateSpecialParticles(e,i,s=1024,y=768){e.forEach(n=>{switch(n.time!==void 0&&(n.time+=i),n.type){case"sleigh":(n.vx>0&&n.x>n.targetX||n.vx<0&&n.x<n.targetX)&&(n.active=!1);break;case"robin":switch(n.state){case"flying_in":const o=n.targetX-n.x,h=n.targetY-n.y,l=Math.sqrt(o*o+h*h);if(l<20)n.state="sitting",n.vx=0,n.vy=0,n.sitStartTime=n.time;else{const P=o/l,M=h/l,m=3;n.vx=P*m;const w=30,S=.008,u=Math.sin(n.time*S+n.waveOffset)*w,v=Math.cos(n.time*S+n.waveOffset)*w*S;n.vy=M*m*.3+v}break;case"sitting":if(n.vx=0,n.vy=0,n.time-n.sitStartTime>n.sitTime){n.state="flying_away";const P=n.x<s/2?-4:4;n.vx=P,n.flyAwayStartTime=n.time}break;case"flying_away":const a=n.vx>0?1:-1;n.vx=a*4;const r=25,g=.01,t=n.time-n.flyAwayStartTime,f=Math.sin(t*g+n.waveOffset)*r,d=Math.cos(t*g+n.waveOffset)*r*g;n.vy=-1.5+d,(n.x<-50||n.x>s+50||n.y<-50)&&(n.active=!1);break}break;case"train":if(n.smoke||(n.smoke=[]),(n.vx>0&&n.x>s+500||n.vx<0&&n.x<-500)&&(n.active=!1),n.lastSmokeTime||(n.lastSmokeTime=0),n.time-n.lastSmokeTime>150){n.lastSmokeTime=n.time;const h=n.size*1.8/20,l=n.vx>0?1:-1,a=h*8,r=h*7,g=h*10,d=-a-h-r-g*2-h*8,P=h*70,M=h*25,w=(P-M)*.7,S=n.x+w*l,u=n.y+d,v={x:S,y:u,vx:(Math.random()-.5)*.8,vy:-.8-Math.random()*.4,size:8+Math.random()*6,opacity:.7+Math.random()*.2,fadeRate:.012+Math.random()*.008};n.smoke.push(v)}n.smoke=n.smoke.filter(o=>(o.x+=o.vx,o.y+=o.vy,o.vy*=.99,o.size*=1.02,o.opacity-=o.fadeRate,o.opacity>0));break;case"firework":if(!n.exploded){const o=n.y<=n.targetY,h=n.time>=n.explosionTime;(o||h)&&(n.exploded=!0,this.explodeFirework(n,e),n.active=!1)}break}})},drawSnowflake(e,i){e.save(),e.translate(i.x,i.y),e.rotate(i.rotation),e.globalAlpha=i.opacity,e.fillStyle="#ffffff",e.strokeStyle="#ffffff",e.lineWidth=Math.max(i.size*.15,.5);const s=6,y=i.size;for(let n=0;n<s;n++){const o=Math.PI*2*n/s;e.beginPath(),e.moveTo(0,0),e.lineTo(Math.cos(o)*y,Math.sin(o)*y),e.stroke();const h=y*.4,l=Math.PI/6,a=Math.cos(o)*(y*.6),r=Math.sin(o)*(y*.6);e.beginPath(),e.moveTo(a,r),e.lineTo(a+Math.cos(o-l)*h,r+Math.sin(o-l)*h),e.stroke(),e.beginPath(),e.moveTo(a,r),e.lineTo(a+Math.cos(o+l)*h,r+Math.sin(o+l)*h),e.stroke()}e.restore()},drawTree(e,i,s){const y=i.x,n=i.y,o=i.size;e.save(),e.translate(y,n),e.rotate(i.rotation),e.fillStyle="#654321",e.beginPath(),e.moveTo(-o*.2,o*.8),e.lineTo(o*.2,o*.8),e.lineTo(o*.15,o*1.3),e.lineTo(-o*.15,o*1.3),e.closePath(),e.fill(),e.strokeStyle="#4a2f1a",e.lineWidth=1;for(let t=0;t<3;t++)e.beginPath(),e.moveTo(-o*.15,o*.9+t*o*.12),e.lineTo(o*.15,o*.9+t*o*.12),e.stroke();e.fillStyle="#228B22";for(let t=0;t<3;t++){const f=t*o*.4,d=o*(1.2-t*.2);e.beginPath(),e.moveTo(0,-f),e.lineTo(-d,o*.3-f),e.lineTo(d,o*.3-f),e.closePath(),e.fill()}e.strokeStyle="#C0C0C0",e.lineWidth=1.5;for(let t=0;t<3;t++){const f=t*o*.4,d=o*(1.2-t*.2);e.beginPath();for(let P=0;P<=6;P++){const M=-d+P/6*d*2,m=o*.15-f+(P%2===0?-o*.1:0);P===0?e.moveTo(M,m):e.lineTo(M,m)}e.stroke()}const h=["#ff0000","#0000ff","#ffd700","#ff69b4","#00ff00"];for(let t=0;t<8;t++){const f=Math.floor(t/3),d=f*o*.4,P=o*(1.2-f*.2)*.7,M=t%3*(Math.PI*2/3)+f*.5,m=Math.cos(M)*P,w=o*.1-d;e.fillStyle=h[t%h.length],e.beginPath(),e.arc(m,w,o*.12,0,Math.PI*2),e.fill(),e.fillStyle="rgba(255,255,255,0.5)",e.beginPath(),e.arc(m-o*.04,w-o*.04,o*.04,0,Math.PI*2),e.fill()}const l=["#ffff00","#ff0000","#00ff00","#0000ff","#ffffff"];for(let t=0;t<12;t++){const f=Math.floor(t/4),d=f*o*.4,P=o*(1.2-f*.2)*.85,M=t%4*(Math.PI*2/4)+f*.3,m=Math.cos(M)*P,w=o*.2-d,S=(Math.sin(s*.003+t*.5)+1)*.5,u=.3+S*.7,v=e.createRadialGradient(m,w,0,m,w,o*.15),T=l[t%l.length];v.addColorStop(0,T),v.addColorStop(1,"rgba(255,255,255,0)"),e.globalAlpha=u,e.fillStyle=v,e.beginPath(),e.arc(m,w,o*.15,0,Math.PI*2),e.fill(),e.globalAlpha=.5+S*.5,e.fillStyle=T,e.beginPath(),e.arc(m,w,o*.08,0,Math.PI*2),e.fill(),e.globalAlpha=1}const a=o*.35,r=-o*1.4,g=e.createRadialGradient(0,r,0,0,r,a*2);g.addColorStop(0,"rgba(255,223,0,1)"),g.addColorStop(.3,"rgba(255,215,0,0.7)"),g.addColorStop(.6,"rgba(255,215,0,0.3)"),g.addColorStop(1,"rgba(255,215,0,0)"),e.fillStyle=g,e.beginPath(),e.arc(0,r,a*2,0,Math.PI*2),e.fill(),e.fillStyle="#FFD700",e.strokeStyle="#FFA500",e.lineWidth=2,e.save(),e.translate(0,r),e.beginPath();for(let t=0;t<10;t++){const f=Math.PI*2*t/10-Math.PI/2,d=t%2===0?a:a*.4,P=Math.cos(f)*d,M=Math.sin(f)*d;t===0?e.moveTo(P,M):e.lineTo(P,M)}e.closePath(),e.fill(),e.stroke(),e.restore(),e.restore()},drawWreath(e,i,s){const y=i.x,n=i.y,o=i.size;e.save(),e.translate(y,n),e.rotate(i.rotation),i.shape.forEach(a=>{e.lineWidth=o*a.thickness,e.strokeStyle=a.color,e.beginPath();const r=a.angle-Math.PI/i.shape.length,g=a.angle+Math.PI/i.shape.length;e.arc(0,0,o*a.radius,r,g),e.stroke()});const h=["#ff0000","#ffff00","#0000ff"];for(let a=0;a<4;a++){const r=a/4*Math.PI*2+i.rotation,g=Math.cos(r)*o,t=Math.sin(r)*o,f=(Math.sin(s*.002+a*.7)+1)/2;if(f>.5){const d=(f-.5)*2,P=h[a%h.length],M=e.createRadialGradient(g,t,0,g,t,o*.15);M.addColorStop(0,P),M.addColorStop(1,"rgba(255,255,255,0)"),e.globalAlpha=d,e.fillStyle=M,e.beginPath(),e.arc(g,t,o*.15,0,Math.PI*2),e.fill(),e.globalAlpha=1}}e.fillStyle="#c00";const l=-o;e.beginPath(),e.ellipse(-o*.3,l,o*.3,o*.4,-.3,0,Math.PI*2),e.fill(),e.beginPath(),e.ellipse(o*.3,l,o*.3,o*.4,.3,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(0,l,o*.2,0,Math.PI*2),e.fill(),e.restore()},drawNorthStar(e,i,s){const y=i.x,n=i.y,o=i.size,h=.8+Math.sin(s*i.twinkleSpeed+i.twinklePhase)*.2;e.save(),e.translate(y,n);const l=o*4*h,a=e.createRadialGradient(0,0,0,0,0,l);a.addColorStop(0,"rgba(220, 230, 240, 0.4)"),a.addColorStop(.4,"rgba(200, 210, 220, 0.2)"),a.addColorStop(1,"rgba(180, 190, 200, 0)"),e.fillStyle=a,e.beginPath(),e.arc(0,0,l,0,Math.PI*2),e.fill();const r=o*1,g=o*1.5,t=o*.12,f=e.createRadialGradient(0,0,0,0,0,o);f.addColorStop(0,"#ffffff"),f.addColorStop(.3,"#f0f0f0"),f.addColorStop(.6,"#c0c0c0"),f.addColorStop(1,"#a0a0a0"),e.shadowColor="rgba(220, 230, 240, 0.9)",e.shadowBlur=o*.8*h,e.fillStyle=f,e.beginPath(),e.moveTo(0,-g),e.lineTo(t,-t),e.lineTo(r,0),e.lineTo(t,t),e.lineTo(0,g),e.lineTo(-t,t),e.lineTo(-r,0),e.lineTo(-t,-t),e.closePath(),e.fill(),e.shadowBlur=o*1.2*h,e.shadowColor="rgba(255, 255, 255, 1)";const d=e.createRadialGradient(0,0,0,0,0,o*.3);d.addColorStop(0,"#ffffff"),d.addColorStop(.6,"#f5f5f5"),d.addColorStop(1,"rgba(200, 200, 200, 0.8)"),e.fillStyle=d,e.beginPath(),e.arc(0,0,o*.3,0,Math.PI*2),e.fill(),e.shadowBlur=0,e.restore()},drawSleigh(e,i){const s=i.x,y=Math.abs(i.targetX-i.startX),n=Math.abs(i.x-i.startX),o=Math.min(1,n/y),h=Math.sin(o*Math.PI)*i.arcHeight,l=i.baseY-h,a=i.size*1.5,r=i.vx>0?1:-1,g=Math.abs(Math.sin(i.time*.007));if(!isFinite(s)||!isFinite(l)||!isFinite(a)){console.warn("[Christmas] Invalid sleigh values:",{x:s,y:l,size:a});return}const t=[{x:4.8,y:0},{x:4,y:-.3},{x:4,y:.3},{x:3.2,y:-.15},{x:3.2,y:.15}];t.forEach((T,F)=>{const b=s+r*(a*T.x),C=T.y*a;e.fillStyle="#9c6e49",e.strokeStyle="#7b563a",e.lineWidth=1,e.beginPath(),e.moveTo(b-r*a*.4,l+C),e.quadraticCurveTo(b,l+C-a*.4,b+r*a*.4,l+C),e.quadraticCurveTo(b,l+C+a*.4,b-r*a*.4,l+C),e.fill(),e.stroke();const R=b+r*a*.5,G=l+C-a*.3;e.beginPath(),e.ellipse(R,G,a*.25,a*.2,0,0,Math.PI*2),e.fill(),e.stroke(),e.strokeStyle="#6e4a2e",e.lineWidth=1.5;const Y=R-r*a*.1,A=G-a*.15;e.beginPath(),e.moveTo(Y,A),e.lineTo(Y-r*a*.2,A-a*.3),e.lineTo(Y-r*a*.1,A-a*.4),e.moveTo(Y-r*a*.2,A-a*.3),e.lineTo(Y-r*a*.3,A-a*.35),e.stroke();const X=l+C+a*.1;e.lineWidth=2.5,e.strokeStyle="#7b563a";const L=g*a*.45;e.beginPath(),e.moveTo(b+r*a*.3,X),e.lineTo(b+r*(a*.3+L),X+a*.3),e.stroke();const W=(1-g)*a*.45;if(e.beginPath(),e.moveTo(b-r*a*.3,X),e.lineTo(b-r*(a*.3+W),X+a*.3),e.stroke(),F===0){const z=R+r*a*.25,B=G;e.fillStyle="#ff0000",e.beginPath(),e.arc(z,B,a*.03,0,Math.PI*2),e.fill();const q=e.createRadialGradient(z,B,0,z,B,a*.1);q.addColorStop(0,"rgba(255,0,0,0.7)"),q.addColorStop(1,"rgba(255,0,0,0)"),e.fillStyle=q,e.beginPath(),e.arc(z,B,a*.1,0,Math.PI*2),e.fill()}}),e.fillStyle="#c00",e.strokeStyle="#FFD700",e.lineWidth=2;const f=l-a*.7,d=s+r*a*1.5,P=s-r*a*.3,M=l+a*.3;e.beginPath(),e.moveTo(d,f),e.quadraticCurveTo(s+r*a,l-a*.2,d-r*a*.8,l),e.lineTo(P,l),e.lineTo(P,M),e.lineTo(d,M),e.closePath(),e.fill(),e.stroke(),e.beginPath(),e.moveTo(P,l),e.quadraticCurveTo(P-r*a*.4,l-a*.5,P,f),e.quadraticCurveTo(P+r*a*.2,l-a*.4,P+r*a*.2,l),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="#a52a2a",e.lineWidth=4;const m=M+a*.2;function w(T){e.beginPath(),e.moveTo(d+r*a*.1,m+T),e.quadraticCurveTo(s,m+T+a*.2,P-r*a*.2,m+T),e.stroke(),e.beginPath(),e.moveTo(d-r*a*.5,M),e.lineTo(d-r*a*.5,m+T),e.stroke(),e.beginPath(),e.moveTo(P+r*a*.5,M),e.lineTo(P+r*a*.5,m+T),e.stroke()}w(0),w(a*.1);const S=s-r*a*.05;e.fillStyle="#5c4033",e.beginPath(),e.moveTo(S-a*.3,l-a*.4),e.quadraticCurveTo(S-a*.6,l-a*.2,S-a*.4,l+a*.1),e.quadraticCurveTo(S,l+a*.3,S+a*.4,l+a*.1),e.quadraticCurveTo(S+a*.6,l-a*.2,S+a*.3,l-a*.4),e.closePath(),e.fill(),e.fillStyle="rgba(255, 255, 255, 0.2)",e.beginPath(),e.ellipse(S+a*.1,l-a*.3,a*.2,a*.1,-Math.PI/4,0,Math.PI*2),e.fill(),e.strokeStyle="#654321",e.lineWidth=1,e.beginPath(),e.arc(S,l-a*.4,a*.2,0,Math.PI,!0),e.stroke();const u=s+r*a*.6,v=l-a*.55;e.fillStyle="#c00",e.beginPath(),e.moveTo(u-a*.2,v),e.lineTo(u+a*.2,v),e.lineTo(u,v-a*.4),e.closePath(),e.fill(),e.fillStyle="#fff",e.fillRect(u-a*.22,v,a*.44,a*.08),e.beginPath(),e.arc(u,v-a*.4,a*.07,0,Math.PI*2),e.fill(),e.fillStyle="#FFD7BA",e.beginPath(),e.arc(u,v+a*.08,a*.18,0,Math.PI*2),e.fill(),e.fillStyle="#fff",e.beginPath(),e.ellipse(u,v+a*.2,a*.25,a*.2,0,0,Math.PI),e.fill(),e.strokeStyle="#654321",e.lineWidth=1,t.forEach(T=>{const F=s+r*(a*T.x),b=T.y*a,C=F+r*a*.5;e.beginPath(),e.moveTo(u,v+a*.1),e.quadraticCurveTo((u+C)/2,l+b-a*.5,C-r*a*.1,l+b-a*.3),e.stroke()})},drawElf(e,i){const s=i.x,y=i.y,n=i.size,o=i.vx>0?1:-1;e.save(),e.translate(s,y);const h=Math.sin(i.time*.05)*(Math.PI/3);e.fillStyle="#004d00",e.fillRect(o*-n*.1,n*.2,n*.2,n*.5+Math.sin(h+Math.PI)*n*.1),e.fillStyle="#4a2c2a",e.beginPath(),e.ellipse(o*0,n*.7+Math.sin(h+Math.PI)*n*.1,n*.3,n*.15,0,0,Math.PI*2),e.fill(),e.fillStyle="#006400",e.fillRect(o*n*.1,n*.2,n*.2,n*.5+Math.sin(h)*n*.1),e.fillStyle="#5d3836",e.beginPath(),e.ellipse(o*n*.2,n*.7+Math.sin(h)*n*.1,n*.3,n*.15,0,0,Math.PI*2),e.fill(),e.fillStyle="#008000",e.beginPath(),e.moveTo(0,-n*.5),e.lineTo(o*n*.4,n*.3),e.lineTo(o*-n*.4,n*.3),e.closePath(),e.fill(),e.fillStyle="#FFD7BA",e.beginPath(),e.arc(0,-n*.6,n*.3,0,Math.PI*2),e.fill(),e.fillStyle="#c00",e.beginPath(),e.moveTo(0,-n*.7),e.lineTo(o*n*.35,-n*.6),e.lineTo(o*-n*.35,-n*.6),e.closePath(),e.fill(),e.beginPath(),e.moveTo(0,-n*.7),e.quadraticCurveTo(o*n*.2,-n*1.1,o*n*.4,-n*1.3),e.stroke(),e.fillStyle="#ffff00",e.beginPath(),e.arc(o*n*.4,-n*1.3,n*.15,0,Math.PI*2),e.fill(),e.restore()},drawTrain(e,i){const s=i.x,y=i.y,n=i.size*1.8,o=i.vx>0?1:-1,h=i.time;i.smoke&&i.smoke.length>0&&i.smoke.forEach(k=>{e.save(),e.globalAlpha=k.opacity;const I=e.createRadialGradient(k.x,k.y,0,k.x,k.y,k.size);I.addColorStop(0,"#CCCCCC"),I.addColorStop(.5,"#AAAAAA"),I.addColorStop(1,"#888888"),e.fillStyle=I,e.beginPath(),e.arc(k.x,k.y,k.size,0,Math.PI*2),e.fill(),e.restore()}),e.save(),e.translate(s,y),o===-1&&e.scale(-1,1);const l=h*.005,a=n/20,r=0,g=a*8,t=a*5,f=a*7,d=a*35,P=-t-a,M=-g-a,m=P-d,w=a*30,S=a*10,u=M-f-S*2,v=a*70,T=a*25,F=v-T,b=a*60,C=a*15;for(let k=1;k<=i.carriages;k++){const I=-(T+k*(b+C)-C);e.fillStyle="#222",e.strokeStyle="#444",e.lineWidth=1,e.beginPath(),e.arc(I+b*.25,-t,t,0,Math.PI*2),e.fill(),e.stroke(),e.beginPath(),e.arc(I+b*.75,-t,t,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#004d00",e.strokeStyle="#DAA520",e.lineWidth=a*.5,e.fillRect(I,m,b,d),e.strokeRect(I,m,b,d);const U=d*.6,_=m+d*.2;e.shadowColor="#F1C40F",e.shadowBlur=15;const c=.8+Math.sin(h*.001+k)*.2;e.fillStyle=`rgba(255, 235, 150, ${c})`;const E=b*.25,$=I+b*.15,j=I+b*.6;e.fillRect($,_,E,U),e.fillRect(j,_,E,U),e.shadowBlur=0,e.fillStyle="rgba(0, 0, 0, 0.4)";const D=E*.2,K=$+E/2,V=_+D*1.8;e.beginPath(),e.arc(K,V,D,0,Math.PI*2),e.fill(),e.fillRect(K-D,V+D,D*2,D*2);const O=E*.18,J=j+E/2,Q=_+O*1.6;e.beginPath(),e.arc(J,Q,O,0,Math.PI*2),e.fill(),e.fillRect(J-O*1.2,Q,O*2.4,O*2.5);const N=a*4;e.fillStyle="#228B22",e.beginPath(),e.arc(I+b/2,m+d/2,N,0,Math.PI*2),e.arc(I+b/2,m+d/2,N*.6,0,Math.PI*2,!0),e.fill(),e.fillStyle="#c00",e.beginPath(),e.arc(I+b/2,m+d/2+N,N*.3,0,Math.PI*2),e.fill(),e.strokeStyle="#111",e.lineWidth=a,e.beginPath();const Z=P+f/2,x=k===1?-T:I+b+C;e.moveTo(I+b,Z),e.lineTo(x,Z),e.stroke()}e.fillStyle="#2C3E50",e.strokeStyle="#555",e.lineWidth=a;const R=[a*15,a*35,a*55];R.forEach(k=>{e.beginPath(),e.arc(k,-g,g,0,Math.PI*2),e.fill(),e.stroke()});const G=-a*10;e.beginPath(),e.arc(G,-t,t,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#1C2833",e.fillRect(-T-a*5,M-f,v+a*5,f),e.fillStyle="#B03A2E",e.strokeStyle="#DAA520",e.lineWidth=a*.7;const Y=M-f-w;e.beginPath(),e.moveTo(-T,Y),e.lineTo(0,Y),e.lineTo(0,M-f),e.lineTo(-T-a*5,M-f),e.closePath(),e.fill(),e.stroke(),e.fillStyle=`rgba(255, 235, 150, ${.8+Math.sin(h*.001)*.2})`,e.fillRect(-T+a*4,Y+a*4,T-a*12,a*10),e.fillStyle="#2C3E50",e.beginPath(),e.moveTo(0,M-f),e.lineTo(F,M-f),e.arc(F,u+S,S,Math.PI/2,-Math.PI/2),e.lineTo(0,u),e.closePath(),e.fill(),e.stroke();const A=u-a*8;e.fillStyle="#17202A",e.beginPath(),e.moveTo(F*.7,u),e.lineTo(F*.7-a*2,A),e.lineTo(F*.7+a*10,A-a*4),e.lineTo(F*.7+a*8,u),e.closePath(),e.fill();const X=F+S,L=u+S,W=a*4,z=e.createRadialGradient(X,L,W*.2,X,L,W*1.5);z.addColorStop(0,"rgba(255, 255, 200, 1)"),z.addColorStop(.4,"rgba(255, 220, 100, 0.8)"),z.addColorStop(1,"rgba(255, 200, 0, 0)"),e.fillStyle=z,e.fillRect(X-W,L-W,W*2,W*2),e.strokeStyle="#99A3A4",e.lineWidth=a*2;const B=Math.sin(l)*g*.6,q=R[1]+Math.cos(l)*g*.6,H=v-a*10,p=M-f+a*2+Math.sin(h*.01)*a;e.beginPath(),e.moveTo(H,p),e.lineTo(q,-g+B),e.stroke(),e.lineWidth=a*1.5,[R[0],R[2]].forEach(k=>{const I=k+Math.cos(l)*g*.6;e.beginPath(),e.moveTo(q,-g+B),e.lineTo(I,-g+B),e.stroke()}),e.restore()},drawFirework(e,i){e.fillStyle=i.color||"#ffffff",e.beginPath(),e.arc(i.x,i.y,i.size,0,Math.PI*2),e.fill()},explodeFirework(e,i){const s=50+Math.random()*50,y=["#ff0000","#ffff00","#00ff00","#0000ff","#ffffff"];for(let n=0;n<s;n++){const o=Math.random()*Math.PI*2,h=Math.random()*5+2;i.push({type:"spark",x:e.x,y:e.y,vx:Math.cos(o)*h,vy:Math.sin(o)*h,size:2+Math.random()*2,opacity:1,active:!0,static:!1,color:y[Math.floor(Math.random()*y.length)]})}},drawRobin(e,i,s){const y=i.x,n=i.y,o=i.size,h=i.vx>=0?1:-1,l=i.state==="flying_in"||i.state==="flying_away",a=i.state==="sitting";i.wingIntensity===void 0&&(i.wingIntensity=l?1:0);const r=l?1:0,g=.02;i.wingIntensity+=(r-i.wingIntensity)*g;let t=0;if(i.wingIntensity>.01){const M=Math.sin(s*.012+i.waveOffset),m=Math.sin(M*Math.PI/2),w=Math.PI/6;t=m*w*i.wingIntensity}e.save(),e.translate(y,n),h===-1&&e.scale(-1,1),e.fillStyle="#A52A2A",e.beginPath(),e.ellipse(0,0,o*.6,o*.8,0,0,Math.PI*2),e.fill(),e.fillStyle="#DC143C",e.beginPath(),e.ellipse(o*.15,o*.1,o*.45,o*.6,0,0,Math.PI*2),e.fill(),e.fillStyle="#654321",e.strokeStyle="#4a2c2a",e.lineWidth=.5,e.beginPath(),e.moveTo(-o*.5,0),e.quadraticCurveTo(-o*.7,-o*.08,-o*.85,0),e.quadraticCurveTo(-o*.7,o*.08,-o*.5,0),e.fill(),e.stroke(),e.fillStyle="#8B4513",e.strokeStyle="#654321",e.lineWidth=1,e.save(),e.translate(-o*.3,-o*.1),e.rotate(t),e.beginPath(),e.ellipse(0,0,o*.45,o*.25,-Math.PI/6,0,Math.PI*2),e.fill(),e.stroke(),e.restore(),e.fillStyle="#A0692F",e.strokeStyle="#654321",e.lineWidth=1,e.save(),e.translate(-o*.3,o*.1),e.rotate(-t),e.beginPath(),e.ellipse(0,0,o*.45,o*.25,Math.PI/6,0,Math.PI*2),e.fill(),e.stroke(),e.restore(),e.fillStyle="#A52A2A",e.beginPath(),e.arc(o*.5,-o*.2,o*.35,0,Math.PI*2),e.fill(),e.fillStyle="#FFD700",e.strokeStyle="#DAA520",e.lineWidth=.5,e.beginPath(),e.moveTo(o*.75,-o*.2),e.lineTo(o*.95,-o*.15),e.lineTo(o*.75,-o*.1),e.closePath(),e.fill(),e.stroke(),e.fillStyle="#000000",e.beginPath(),e.arc(o*.6,-o*.25,o*.08,0,Math.PI*2),e.fill(),e.fillStyle="#FFFFFF",e.beginPath(),e.arc(o*.62,-o*.27,o*.03,0,Math.PI*2),e.fill(),a&&(e.strokeStyle="#8B4513",e.lineWidth=o*.08,e.lineCap="round",e.beginPath(),e.moveTo(o*.1,o*.7),e.lineTo(o*.1,o*1),e.stroke(),e.beginPath(),e.moveTo(-o*.1,o*.7),e.lineTo(-o*.1,o*1),e.stroke(),e.lineWidth=o*.06,e.beginPath(),e.moveTo(o*.1,o*1),e.lineTo(o*.25,o*1),e.stroke(),e.beginPath(),e.moveTo(-o*.1,o*1),e.lineTo(-o*.25,o*1),e.stroke());const f=o*.5,d=-o*.55;e.fillStyle="#c00",e.beginPath(),e.moveTo(f-o*.3,d),e.lineTo(f+o*.25,d),e.lineTo(f+o*.1,d-o*.5),e.closePath(),e.fill(),e.fillStyle="#FFFFFF",e.fillRect(f-o*.32,d,o*.58,o*.1),e.beginPath(),e.arc(f+o*.1,d-o*.5,o*.1,0,Math.PI*2),e.fill(),e.restore()},drawSnowman(e,i,s){const y=i.x,n=i.y,o=i.size,h=Math.sin(s*.002+i.wavePhase)*.03;e.save(),e.translate(y,n),e.rotate(h),e.fillStyle="rgba(0, 0, 0, 0.2)",e.beginPath(),e.ellipse(0,o*2,o*1.2,o*.3,0,0,Math.PI*2),e.fill(),e.fillStyle="#FFFFFF",e.strokeStyle="#E0E0E0",e.lineWidth=1,e.beginPath(),e.arc(0,o*1.3,o*1,0,Math.PI*2),e.fill(),e.stroke(),e.beginPath(),e.arc(0,o*.3,o*.7,0,Math.PI*2),e.fill(),e.stroke(),e.beginPath(),e.arc(0,-o*.6,o*.5,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#000000",e.beginPath(),e.arc(0,o*.5,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(0,o*.1,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(0,-o*.2,o*.08,0,Math.PI*2),e.fill(),e.strokeStyle="#654321",e.lineWidth=o*.1,e.beginPath(),e.moveTo(-o*.6,o*.3),e.lineTo(-o*1.2,o*0),e.lineTo(-o*1.5,-o*.1),e.stroke(),e.lineWidth=o*.05,e.beginPath(),e.moveTo(-o*1.5,-o*.1),e.lineTo(-o*1.7,-o*.3),e.stroke(),e.beginPath(),e.moveTo(-o*1.5,-o*.1),e.lineTo(-o*1.8,-o*.05),e.stroke(),e.lineWidth=o*.1,e.beginPath(),e.moveTo(o*.6,o*.3),e.lineTo(o*1.2,o*0),e.lineTo(o*1.5,-o*.1),e.stroke(),e.lineWidth=o*.05,e.beginPath(),e.moveTo(o*1.5,-o*.1),e.lineTo(o*1.7,-o*.3),e.stroke(),e.beginPath(),e.moveTo(o*1.5,-o*.1),e.lineTo(o*1.8,-o*.05),e.stroke(),e.fillStyle="#FF6347",e.beginPath(),e.moveTo(o*.15,-o*.6),e.lineTo(o*.6,-o*.65),e.lineTo(o*.15,-o*.55),e.closePath(),e.fill(),e.fillStyle="#000000",e.beginPath(),e.arc(-o*.15,-o*.7,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(o*.15,-o*.7,o*.08,0,Math.PI*2),e.fill(),[{x:-o*.2,y:-o*.4},{x:-o*.1,y:-o*.35},{x:0,y:-o*.33},{x:o*.1,y:-o*.35},{x:o*.2,y:-o*.4}].forEach(a=>{e.beginPath(),e.arc(a.x,a.y,o*.05,0,Math.PI*2),e.fill()}),e.fillStyle="#c00",e.strokeStyle="#8B0000",e.lineWidth=1,e.beginPath(),e.ellipse(0,-o*.15,o*.55,o*.15,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillRect(o*.3,-o*.1,o*.2,o*.8),e.strokeRect(o*.3,-o*.1,o*.2,o*.8),e.strokeStyle="#8B0000",e.lineWidth=o*.03;for(let a=0;a<4;a++)e.beginPath(),e.moveTo(o*.33+a*o*.13,o*.7),e.lineTo(o*.33+a*o*.13,o*.85),e.stroke();e.fillStyle="#000000",e.strokeStyle="#333333",e.lineWidth=2,e.beginPath(),e.ellipse(0,-o*1.1,o*.7,o*.15,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillRect(-o*.45,-o*1.8,o*.9,o*.7),e.strokeRect(-o*.45,-o*1.8,o*.9,o*.7),e.fillStyle="#c00",e.fillRect(-o*.45,-o*1.3,o*.9,o*.15),e.fillStyle="#228B22",e.beginPath(),e.arc(-o*.15,-o*1.22,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(o*.15,-o*1.22,o*.08,0,Math.PI*2),e.fill(),e.fillStyle="#FF0000",e.beginPath(),e.arc(0,-o*1.25,o*.06,0,Math.PI*2),e.fill(),e.restore()}};
1
+ import{createParticle as k}from"./../core/particles.js";import{drawSleigh as T,drawTrain as I}from"./christmas-vehicles.js";import{drawTree as z,drawWreath as C,drawRobin as F,drawElf as b,TREE_GIFTS as R}from"./christmas-festive.js";import{spawnElfAntic as E,updateElfAntic as Y}from"./christmas-antics.js";export default{name:"christmas",displayName:"Christmas",emoji:"\u{1F384}",intensityConfig:{light:{count:50,speedRange:[.5,1.5],sizeRange:[1,3],trees:3,wreaths:2,northStars:1,snowmen:2},medium:{count:150,speedRange:[.8,2.5],sizeRange:[1,4],trees:6,wreaths:3,northStars:1,snowmen:3},heavy:{count:300,speedRange:[1,3.5],sizeRange:[1,5],trees:10,wreaths:4,northStars:1,snowmen:4}},particles:["snowflake"],decorations:["tree","wreath","sleigh","robin","train","elf","firework","north-star","snowman","peeing-elf","thieving-elf"],decorationKeys:["trees","wreaths","northStars","snowmen","sleigh","elf","train","firework","robin","peeingElfChance","thievingElfChance"],optInDecorations:{peeingElf:{chance:"peeingElfChance",value:7e-4},thievingElf:{chance:"thievingElfChance",value:.001}},colors:{primary:"#ffffff",secondary:"#228B22",accent:"#c00",gold:"#FFD700"},createSnowflakeParticle(e,t,n){const s=k(n,e,t);return s.type="snowflake",s},createFallingParticle(e,t,n){return this.createSnowflakeParticle(e,t,n)},createTree(e,t,n={}){return{type:"tree",gifts:R.map(s=>({...s,stolen:!1,reserved:!1})),x:n.x!==void 0?n.x:Math.random()*e,y:n.y!==void 0?n.y:Math.random()*t,vx:0,vy:0,size:20+Math.random()*15,opacity:.6+Math.random()*.3,rotation:0,rotationSpeed:0,active:!0,static:!0}},createWreath(e,t,n={}){const s=[];for(let o=0;o<20;o++){const r=o/20*Math.PI*2;s.push({angle:r,radius:.9+Math.random()*.2,thickness:.2+Math.random()*.15,color:o%2===0?"#1a6b1a":"#228B22"})}return{type:"wreath",x:n.x!==void 0?n.x:Math.random()*e,y:n.y!==void 0?n.y:Math.random()*t,vx:0,vy:0,size:15+Math.random()*10,opacity:.7+Math.random()*.2,rotation:0,rotationSpeed:0,active:!0,static:!0,shape:s}},createNorthStar(e,t,n={}){return{type:"north-star",x:n.x!==void 0?n.x:e/2,y:n.y!==void 0?n.y:80,vx:0,vy:0,size:25,opacity:1,twinklePhase:Math.random()*Math.PI*2,twinkleSpeed:.003,active:!0,static:!0}},createSnowman(e,t,n={}){return{type:"snowman",x:n.x!==void 0?n.x:Math.random()*e,y:n.y!==void 0?n.y:t-50,vx:0,vy:0,size:15+Math.random()*10,opacity:1,time:Math.random()*1e3,wavePhase:Math.random()*Math.PI*2,active:!0,static:!0}},createInitialDecorations(e,t,n){const s=[],a=n.trees??6;for(let i=0;i<a;i++)s.push(this.createTree(e,t,{x:e/(a+1)*(i+1),y:t-60-Math.random()*20}));const o=n.wreaths??3;for(let i=0;i<o;i++)s.push(this.createWreath(e,t,{x:e/(o+1)*(i+1),y:50+Math.random()*100}));n.northStars!==0&&n.northStars&&s.push(this.createNorthStar(e,t,{x:e/2,y:60}));const r=n.snowmen??3;for(let i=0;i<r;i++)s.push(this.createSnowman(e,t,{x:e/(r+1)*(i+1),y:t-50-Math.random()*10}));return s},spawnSpecialParticle(e,t,n,s={}){const a=E(e,t,n,s);if(a)return a;const o=Math.random();if(o<5e-4){if(s.sleigh===0||e.some(f=>f.type==="sleigh"))return null;const r=Math.random()<.5,i=r?-100:t+100,h=100+Math.random()*(n*.3);return{type:"sleigh",x:i,startX:i,y:h,baseY:h,targetX:r?t+100:-100,canvasWidth:t,vx:r?3+Math.random()*2:-(3+Math.random()*2),vy:0,arcHeight:150+Math.random()*100,time:0,size:15+Math.random()*10,opacity:.9,rotation:0,active:!0,static:!1}}else if(o<.0013&&s.elf!==0){const r=Math.random()<.5;return{type:"elf",x:r?-50:t+50,y:n-30,baseY:n-30,vx:r?1.5+Math.random()*1:-(1.5+Math.random()*1),waveAmplitude:3,waveFrequency:.05,waveOffset:Math.random()*Math.PI*2,time:0,size:10+Math.random()*5,opacity:.95,rotation:0,active:!0,static:!1}}else if(o<.005&&s.train!==0){if(e.some(l=>l.type==="train"))return null;const r=Math.random()<.5,i=r?-500:t+500,h=21+Math.random()*9,f=h*.72,d=n-f-10;return{type:"train",x:i,y:d,baseY:d,vx:r?4+Math.random()*2:-(4+Math.random()*2),vy:0,size:h,opacity:1,time:0,smoke:[],active:!0,static:!1,carriages:2+Math.floor(Math.random()*2)}}else{if(o<.008&&s.firework!==0)return{type:"firework",x:Math.random()*t,y:n,vx:(Math.random()-.5)*4,vy:-10-Math.random()*5,size:2+Math.random()*2,opacity:1,active:!0,static:!1,time:0,exploded:!1,explosionTime:30+Math.random()*30};if(o<.012&&s.robin!==0){if(e.some(m=>m.type==="robin"))return null;const r=Math.random()<.5,i=Math.random()*(n*.2),h=r?-50:t+50,f=10+Math.random()*5,d=Math.max(f*3,Math.min(n*.6-f*2,n*.2+Math.random()*(n*.4))),l=Math.random()*(t*.6)+t*.2;return{type:"robin",state:"flying_in",x:h,y:i,startX:h,startY:i,targetX:l,targetY:d,vx:r?.5+Math.random()*.5:-(.5+Math.random()*.5),vy:0,size:f,opacity:.95,active:!0,static:!1,sitTime:3e3+Math.random()*2e3,sitStartTime:0,flightProgress:0,time:0,waveOffset:Math.random()*Math.PI*2}}}return null},updateSpecialParticles(e,t,n=1024,s=768){e.forEach(a=>{switch(a.time!==void 0&&(a.time+=t),a.type){case"peeing-elf":case"thieving-elf":Y(a,t,n);break;case"sleigh":(a.vx>0&&a.x>a.targetX||a.vx<0&&a.x<a.targetX)&&(a.active=!1);break;case"robin":switch(a.state){case"flying_in":const o=a.targetX-a.x,r=a.targetY-a.y,i=Math.sqrt(o*o+r*r);if(i<20)a.state="sitting",a.vx=0,a.vy=0,a.sitStartTime=a.time;else{const M=o/i,u=r/i,v=3;a.vx=M*v;const w=30,g=.008,S=Math.sin(a.time*g+a.waveOffset)*w,P=Math.cos(a.time*g+a.waveOffset)*w*g;a.vy=u*v*.3+P}break;case"sitting":if(a.vx=0,a.vy=0,a.time-a.sitStartTime>a.sitTime){a.state="flying_away";const M=a.x<n/2?-4:4;a.vx=M,a.flyAwayStartTime=a.time}break;case"flying_away":const h=a.vx>0?1:-1;a.vx=h*4;const f=25,d=.01,l=a.time-a.flyAwayStartTime,m=Math.sin(l*d+a.waveOffset)*f,y=Math.cos(l*d+a.waveOffset)*f*d;a.vy=-1.5+y,(a.x<-50||a.x>n+50||a.y<-50)&&(a.active=!1);break}break;case"train":if(a.smoke||(a.smoke=[]),(a.vx>0&&a.x>n+500||a.vx<0&&a.x<-500)&&(a.active=!1),a.lastSmokeTime||(a.lastSmokeTime=0),a.time-a.lastSmokeTime>150){a.lastSmokeTime=a.time;const r=a.size*1.8/20,i=a.vx>0?1:-1,h=r*8,f=r*7,d=r*10,y=-h-r-f-d*2-r*8,M=r*70,u=r*25,w=(M-u)*.7,g=a.x+w*i,S=a.y+y,P={x:g,y:S,vx:(Math.random()-.5)*.8,vy:-.8-Math.random()*.4,size:8+Math.random()*6,opacity:.7+Math.random()*.2,fadeRate:.012+Math.random()*.008};a.smoke.push(P)}a.smoke=a.smoke.filter(o=>(o.x+=o.vx,o.y+=o.vy,o.vy*=.99,o.size*=1.02,o.opacity-=o.fadeRate,o.opacity>0));break;case"firework":if(!a.exploded){const o=a.y<=a.targetY,r=a.time>=a.explosionTime;(o||r)&&(a.exploded=!0,this.explodeFirework(a,e),a.active=!1)}break}})},drawSnowflake(e,t){e.save(),e.translate(t.x,t.y),e.rotate(t.rotation),e.globalAlpha=t.opacity,e.fillStyle="#ffffff",e.strokeStyle="#ffffff",e.lineWidth=Math.max(t.size*.15,.5);const n=6,s=t.size;for(let a=0;a<n;a++){const o=Math.PI*2*a/n;e.beginPath(),e.moveTo(0,0),e.lineTo(Math.cos(o)*s,Math.sin(o)*s),e.stroke();const r=s*.4,i=Math.PI/6,h=Math.cos(o)*(s*.6),f=Math.sin(o)*(s*.6);e.beginPath(),e.moveTo(h,f),e.lineTo(h+Math.cos(o-i)*r,f+Math.sin(o-i)*r),e.stroke(),e.beginPath(),e.moveTo(h,f),e.lineTo(h+Math.cos(o+i)*r,f+Math.sin(o+i)*r),e.stroke()}e.restore()},drawTree:z,drawWreath:C,drawNorthStar(e,t,n){const s=t.x,a=t.y,o=t.size,r=.8+Math.sin(n*t.twinkleSpeed+t.twinklePhase)*.2;e.save(),e.translate(s,a);const i=o*4*r,h=e.createRadialGradient(0,0,0,0,0,i);h.addColorStop(0,"rgba(220, 230, 240, 0.4)"),h.addColorStop(.4,"rgba(200, 210, 220, 0.2)"),h.addColorStop(1,"rgba(180, 190, 200, 0)"),e.fillStyle=h,e.beginPath(),e.arc(0,0,i,0,Math.PI*2),e.fill();const f=o*1,d=o*1.5,l=o*.12,m=e.createRadialGradient(0,0,0,0,0,o);m.addColorStop(0,"#ffffff"),m.addColorStop(.3,"#f0f0f0"),m.addColorStop(.6,"#c0c0c0"),m.addColorStop(1,"#a0a0a0"),e.shadowColor="rgba(220, 230, 240, 0.9)",e.shadowBlur=o*.8*r,e.fillStyle=m,e.beginPath(),e.moveTo(0,-d),e.lineTo(l,-l),e.lineTo(f,0),e.lineTo(l,l),e.lineTo(0,d),e.lineTo(-l,l),e.lineTo(-f,0),e.lineTo(-l,-l),e.closePath(),e.fill(),e.shadowBlur=o*1.2*r,e.shadowColor="rgba(255, 255, 255, 1)";const y=e.createRadialGradient(0,0,0,0,0,o*.3);y.addColorStop(0,"#ffffff"),y.addColorStop(.6,"#f5f5f5"),y.addColorStop(1,"rgba(200, 200, 200, 0.8)"),e.fillStyle=y,e.beginPath(),e.arc(0,0,o*.3,0,Math.PI*2),e.fill(),e.shadowBlur=0,e.restore()},drawSleigh:T,drawElf:b,drawPeeingElf:b,drawThievingElf:b,drawTrain:I,drawFirework(e,t){e.fillStyle=t.color||"#ffffff",e.beginPath(),e.arc(t.x,t.y,t.size,0,Math.PI*2),e.fill()},explodeFirework(e,t){const n=50+Math.random()*50,s=["#ff0000","#ffff00","#00ff00","#0000ff","#ffffff"];for(let a=0;a<n;a++){const o=Math.random()*Math.PI*2,r=Math.random()*5+2;t.push({type:"spark",x:e.x,y:e.y,vx:Math.cos(o)*r,vy:Math.sin(o)*r,size:2+Math.random()*2,opacity:1,active:!0,static:!1,color:s[Math.floor(Math.random()*s.length)]})}},drawRobin:F,drawSnowman(e,t,n){const s=t.x,a=t.y,o=t.size,r=Math.sin(n*.002+t.wavePhase)*.03;e.save(),e.translate(s,a),e.rotate(r),e.fillStyle="rgba(0, 0, 0, 0.2)",e.beginPath(),e.ellipse(0,o*2,o*1.2,o*.3,0,0,Math.PI*2),e.fill(),e.fillStyle="#FFFFFF",e.strokeStyle="#E0E0E0",e.lineWidth=1,e.beginPath(),e.arc(0,o*1.3,o*1,0,Math.PI*2),e.fill(),e.stroke(),e.beginPath(),e.arc(0,o*.3,o*.7,0,Math.PI*2),e.fill(),e.stroke(),e.beginPath(),e.arc(0,-o*.6,o*.5,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#000000",e.beginPath(),e.arc(0,o*.5,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(0,o*.1,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(0,-o*.2,o*.08,0,Math.PI*2),e.fill(),e.strokeStyle="#654321",e.lineWidth=o*.1,e.beginPath(),e.moveTo(-o*.6,o*.3),e.lineTo(-o*1.2,o*0),e.lineTo(-o*1.5,-o*.1),e.stroke(),e.lineWidth=o*.05,e.beginPath(),e.moveTo(-o*1.5,-o*.1),e.lineTo(-o*1.7,-o*.3),e.stroke(),e.beginPath(),e.moveTo(-o*1.5,-o*.1),e.lineTo(-o*1.8,-o*.05),e.stroke(),e.lineWidth=o*.1,e.beginPath(),e.moveTo(o*.6,o*.3),e.lineTo(o*1.2,o*0),e.lineTo(o*1.5,-o*.1),e.stroke(),e.lineWidth=o*.05,e.beginPath(),e.moveTo(o*1.5,-o*.1),e.lineTo(o*1.7,-o*.3),e.stroke(),e.beginPath(),e.moveTo(o*1.5,-o*.1),e.lineTo(o*1.8,-o*.05),e.stroke(),e.fillStyle="#FF6347",e.beginPath(),e.moveTo(o*.15,-o*.6),e.lineTo(o*.6,-o*.65),e.lineTo(o*.15,-o*.55),e.closePath(),e.fill(),e.fillStyle="#000000",e.beginPath(),e.arc(-o*.15,-o*.7,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(o*.15,-o*.7,o*.08,0,Math.PI*2),e.fill(),[{x:-o*.2,y:-o*.4},{x:-o*.1,y:-o*.35},{x:0,y:-o*.33},{x:o*.1,y:-o*.35},{x:o*.2,y:-o*.4}].forEach(h=>{e.beginPath(),e.arc(h.x,h.y,o*.05,0,Math.PI*2),e.fill()}),e.fillStyle="#c00",e.strokeStyle="#8B0000",e.lineWidth=1,e.beginPath(),e.ellipse(0,-o*.15,o*.55,o*.15,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillRect(o*.3,-o*.1,o*.2,o*.8),e.strokeRect(o*.3,-o*.1,o*.2,o*.8),e.strokeStyle="#8B0000",e.lineWidth=o*.03;for(let h=0;h<4;h++)e.beginPath(),e.moveTo(o*.33+h*o*.13,o*.7),e.lineTo(o*.33+h*o*.13,o*.85),e.stroke();e.fillStyle="#000000",e.strokeStyle="#333333",e.lineWidth=2,e.beginPath(),e.ellipse(0,-o*1.1,o*.7,o*.15,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillRect(-o*.45,-o*1.8,o*.9,o*.7),e.strokeRect(-o*.45,-o*1.8,o*.9,o*.7),e.fillStyle="#c00",e.fillRect(-o*.45,-o*1.3,o*.9,o*.15),e.fillStyle="#228B22",e.beginPath(),e.arc(-o*.15,-o*1.22,o*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(o*.15,-o*1.22,o*.08,0,Math.PI*2),e.fill(),e.fillStyle="#FF0000",e.beginPath(),e.arc(0,-o*1.25,o*.06,0,Math.PI*2),e.fill(),e.restore()}};
@@ -1 +1 @@
1
- (function(){"use strict";fetch("/api/effects/settings").then(function(y){return y.json()}).then(function(y){var z=y.respectMotion!==!1,A=y.defaultDuration||600,S=y.defaultAnimation||"fade",E=y.defaultThreshold!=null?y.defaultThreshold:.1,p=z&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,d=document.querySelector(".page-body");if(!d)return;function r(t,e){return t.getAttribute("data-fx-"+e)}function i(t,e,n){var a=r(t,e);return a!==null?parseFloat(a):n}function v(t,e,n){var a=r(t,e);return a===null?n:a==="true"||a==="1"}function b(t,e,n){var a=new IntersectionObserver(function(o){o[0].isIntersecting&&(a.unobserve(t),n())},{threshold:e});a.observe(t)}d.querySelectorAll(".dm-fx-reveal").forEach(function(t){var e=r(t,"animation")||S,n=i(t,"duration",A),a=i(t,"delay",0),o=i(t,"threshold",E),f=v(t,"once",!0);p||(t.style.opacity="0",t.style.transition="none",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"&&(t.style.transform="rotateY(90deg)"),requestAnimationFrame(function(){requestAnimationFrame(function(){t.style.transition="opacity "+n+"ms ease "+a+"ms, transform "+n+"ms ease "+a+"ms";var s=new IntersectionObserver(function(u){u.forEach(function(h){h.isIntersecting?(t.style.opacity="1",t.style.transform="",f&&s.unobserve(t)):f||(t.style.opacity="0",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"?t.style.transform="rotateY(90deg)":t.style.transform="")})},{threshold:o});s.observe(t)})}))}),d.querySelectorAll(".dm-fx-counter").forEach(function(t){var e=i(t,"to",0),n=i(t,"from",0),a=i(t,"duration",2e3),o=r(t,"prefix")||"",f=r(t,"suffix")||"",s=i(t,"decimals",0),u=r(t,"separator")||"";function h(m){var l=m.toFixed(s);if(u){var x=l.split(".");x[0]=x[0].replace(/\B(?=(\d{3})+(?!\d))/g,u),l=x.join(".")}return o+l+f}if(p){t.textContent=h(e);return}b(t,.5,function(){var m=null;requestAnimationFrame(function l(x){m||(m=x);var w=Math.min((x-m)/a,1),q=1-Math.pow(1-w,3);t.textContent=h(n+(e-n)*q),w<1&&requestAnimationFrame(l)})})}),p||d.querySelectorAll(".dm-fx-scramble").forEach(function(t){var e=i(t,"speed",50),n=t.textContent,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&",o=0;b(t,.3,function(){var f=setInterval(function(){for(var s="",u=0;u<n.length;u++)u<o||n[u]===" "?s+=n[u]:s+=a[Math.floor(Math.random()*a.length)];t.textContent=s,o<n.length?o++:clearInterval(f)},e)})}),p||d.querySelectorAll(".dm-fx-shake").forEach(function(t){var e=i(t,"intensity",5),n=i(t,"duration",500),a=r(t,"direction")||"horizontal",o=a==="vertical"?"translateY":"translateX";t.animate([{transform:o+"(0)"},{transform:o+"(-"+e+"px)"},{transform:o+"("+e+"px)"},{transform:o+"(-"+e+"px)"},{transform:o+"("+e+"px)"},{transform:o+"(0)"}],{duration:n,easing:"ease-in-out"})}),d.querySelectorAll(".dm-fx-ripple").forEach(function(t){t.style.position="relative",t.style.overflow="hidden",t.addEventListener("click",function(e){var n=r(t,"colour")||"rgba(255,255,255,0.3)",a=i(t,"duration",600),o=t.getBoundingClientRect(),f=Math.max(o.width,o.height)*2,s=document.createElement("span");s.style.cssText="position:absolute;border-radius:50%;pointer-events:none;width:"+f+"px;height:"+f+"px;left:"+(e.clientX-o.left-f/2)+"px;top:"+(e.clientY-o.top-f/2)+"px;background:"+n+";transform:scale(0);opacity:1;transition:transform "+a+"ms ease,opacity "+a+"ms ease;",t.appendChild(s),requestAnimationFrame(function(){s.style.transform="scale(1)",s.style.opacity="0"}),setTimeout(function(){s.remove()},a)})});var c=window.Domma&&window.Domma.effects;c&&(d.querySelectorAll(".dm-fx-breathe").forEach(function(t){if(!p){var e={};r(t,"amplitude")&&(e.amplitude=i(t,"amplitude",6)),r(t,"duration")&&(e.duration=i(t,"duration",3e3)),r(t,"easing")&&(e.easing=r(t,"easing")),r(t,"stagger")&&(e.stagger=i(t,"stagger",0)),c.breathe(t,e)}}),d.querySelectorAll(".dm-fx-pulse").forEach(function(t){if(!p){var e={};r(t,"scale")&&(e.scale=i(t,"scale",1.05)),r(t,"duration")&&(e.duration=i(t,"duration",2e3)),r(t,"easing")&&(e.easing=r(t,"easing")),c.pulse(t,e)}}),d.querySelectorAll(".dm-fx-scribe").forEach(function(t){var e={};r(t,"speed")&&(e.speed=i(t,"speed",50)),r(t,"delete-speed")&&(e.deleteSpeed=i(t,"delete-speed",30)),r(t,"cursor")&&(e.cursor=v(t,"cursor",!0)),r(t,"cursor-char")&&(e.cursorChar=r(t,"cursor-char")),r(t,"cursor-type")&&(e.cursorType=r(t,"cursor-type")),r(t,"mode")&&(e.mode=r(t,"mode")),r(t,"loop")&&(e.loop=v(t,"loop",!1)),r(t,"loop-delay")&&(e.loopDelay=i(t,"loop-delay",1e3)),r(t,"pause-on-hover")&&(e.pauseOnHover=v(t,"pause-on-hover",!1));var n=r(t,"actions");if(n)try{e.actions=JSON.parse(n)}catch{return}else{var a=t.textContent.trim();if(!a)return;t.textContent="",e.actions=[{render:a}]}c.scribe(t,e)}),d.querySelectorAll(".dm-fx-twinkle").forEach(function(t){var e={};r(t,"count")&&(e.count=i(t,"count",100)),r(t,"shape")&&(e.shape=r(t,"shape")),r(t,"colour")&&(e.colour=r(t,"colour")),r(t,"min-size")&&(e.minSize=i(t,"min-size",1)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",3)),c.twinkle(t,e)}),typeof c.tickerTape=="function"&&d.querySelectorAll(".dm-fx-ticker-tape").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",50)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"sway")&&(e.sway=i(t,"sway",60)),r(t,"rotation-speed")&&(e.rotationSpeed=i(t,"rotation-speed",6)),r(t,"min-width")&&(e.minWidth=i(t,"min-width",5)),r(t,"max-width")&&(e.maxWidth=i(t,"max-width",9)),r(t,"min-height")&&(e.minHeight=i(t,"min-height",12)),r(t,"max-height")&&(e.maxHeight=i(t,"max-height",22)),r(t,"fade-start")&&(e.fadeStart=i(t,"fade-start",.55)),r(t,"burst")&&(e.burst=v(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",150)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?c.tickerTape(null,e):c.tickerTape(t,e)}}),typeof c.butterflies=="function"&&d.querySelectorAll(".dm-fx-butterflies").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",18)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"wander")&&(e.wander=i(t,"wander",1)),r(t,"rise-speed")&&(e.riseSpeed=i(t,"rise-speed",.4)),r(t,"flap-speed")&&(e.flapSpeed=i(t,"flap-speed",1)),r(t,"min-size")&&(e.minSize=i(t,"min-size",14)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",26)),r(t,"two-tone")&&(e.twoTone=v(t,"two-tone",!0)),r(t,"burst")&&(e.burst=v(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",40)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?c.butterflies(null,e):c.butterflies(t,e)}}),typeof c.strobe=="function"&&d.querySelectorAll(".dm-fx-strobe").forEach(function(t){if(p)return;function e(s){var u=r(t,s);if(!u)return null;var h=u.split(",").map(function(m){return m.trim()}).filter(Boolean);return h.length?h:null}var n={};r(t,"preset")&&(n.preset=r(t,"preset")),r(t,"motion")&&(n.motion=r(t,"motion")),r(t,"speed")&&(n.speed=i(t,"speed",1)),r(t,"sweep-arc")&&(n.sweepArc=i(t,"sweep-arc",100)),r(t,"beam-width")&&(n.beamWidth=i(t,"beam-width",16)),r(t,"intensity")&&(n.intensity=i(t,"intensity",.7)),r(t,"flicker")&&(n.flicker=v(t,"flicker",!0)),r(t,"hz")&&(n.hz=i(t,"hz",10));var a=e("origins");a&&(n.origins=a);var o=e("colours");o&&(n.colours=o),r(t,"duration")&&(n.duration=i(t,"duration",null)),r(t,"z-index")&&(n.zIndex=i(t,"z-index",9999));var f=r(t,"mode");f==="page"?c.strobe(null,n):c.strobe(t,n)}));var g=d.querySelectorAll(".dm-fx-celebrate");g.length&&!p&&import("/public/js/celebrations/index.js?v=20260930-snow").then(function(t){var e=t.CelebrationsEffect;g.forEach(function(n){var a=n.getAttribute("data-fx-theme")||"auto",o=n.getAttribute("data-fx-intensity")||"medium",f=n.getAttribute("data-fx-z-index")?parseInt(n.getAttribute("data-fx-z-index"),10):999;if(!(a==="auto"&&(a=e.getCurrentTheme(),!a))){var s={},u=["data-fx-theme","data-fx-intensity","data-fx-z-index"];Array.from(n.attributes).forEach(function(m){m.name.startsWith("data-fx-")&&!u.includes(m.name)&&(s[m.name.slice(8)]=m.value)});var h=new e({theme:a,intensity:o,enabled:!0,zIndex:f,overrides:s});h.init().then(function(){h.start()})}})}).catch(function(){})}).catch(function(){})})();
1
+ (function(){"use strict";fetch("/api/effects/settings").then(function(y){return y.json()}).then(function(y){var z=y.respectMotion!==!1,A=y.defaultDuration||600,S=y.defaultAnimation||"fade",E=y.defaultThreshold!=null?y.defaultThreshold:.1,p=z&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,d=document.querySelector(".page-body");if(!d)return;function r(t,e){return t.getAttribute("data-fx-"+e)}function i(t,e,n){var a=r(t,e);return a!==null?parseFloat(a):n}function v(t,e,n){var a=r(t,e);return a===null?n:a==="true"||a==="1"}function b(t,e,n){var a=new IntersectionObserver(function(o){o[0].isIntersecting&&(a.unobserve(t),n())},{threshold:e});a.observe(t)}d.querySelectorAll(".dm-fx-reveal").forEach(function(t){var e=r(t,"animation")||S,n=i(t,"duration",A),a=i(t,"delay",0),o=i(t,"threshold",E),f=v(t,"once",!0);p||(t.style.opacity="0",t.style.transition="none",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"&&(t.style.transform="rotateY(90deg)"),requestAnimationFrame(function(){requestAnimationFrame(function(){t.style.transition="opacity "+n+"ms ease "+a+"ms, transform "+n+"ms ease "+a+"ms";var s=new IntersectionObserver(function(u){u.forEach(function(h){h.isIntersecting?(t.style.opacity="1",t.style.transform="",f&&s.unobserve(t)):f||(t.style.opacity="0",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"?t.style.transform="rotateY(90deg)":t.style.transform="")})},{threshold:o});s.observe(t)})}))}),d.querySelectorAll(".dm-fx-counter").forEach(function(t){var e=i(t,"to",0),n=i(t,"from",0),a=i(t,"duration",2e3),o=r(t,"prefix")||"",f=r(t,"suffix")||"",s=i(t,"decimals",0),u=r(t,"separator")||"";function h(m){var l=m.toFixed(s);if(u){var x=l.split(".");x[0]=x[0].replace(/\B(?=(\d{3})+(?!\d))/g,u),l=x.join(".")}return o+l+f}if(p){t.textContent=h(e);return}b(t,.5,function(){var m=null;requestAnimationFrame(function l(x){m||(m=x);var w=Math.min((x-m)/a,1),q=1-Math.pow(1-w,3);t.textContent=h(n+(e-n)*q),w<1&&requestAnimationFrame(l)})})}),p||d.querySelectorAll(".dm-fx-scramble").forEach(function(t){var e=i(t,"speed",50),n=t.textContent,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&",o=0;b(t,.3,function(){var f=setInterval(function(){for(var s="",u=0;u<n.length;u++)u<o||n[u]===" "?s+=n[u]:s+=a[Math.floor(Math.random()*a.length)];t.textContent=s,o<n.length?o++:clearInterval(f)},e)})}),p||d.querySelectorAll(".dm-fx-shake").forEach(function(t){var e=i(t,"intensity",5),n=i(t,"duration",500),a=r(t,"direction")||"horizontal",o=a==="vertical"?"translateY":"translateX";t.animate([{transform:o+"(0)"},{transform:o+"(-"+e+"px)"},{transform:o+"("+e+"px)"},{transform:o+"(-"+e+"px)"},{transform:o+"("+e+"px)"},{transform:o+"(0)"}],{duration:n,easing:"ease-in-out"})}),d.querySelectorAll(".dm-fx-ripple").forEach(function(t){t.style.position="relative",t.style.overflow="hidden",t.addEventListener("click",function(e){var n=r(t,"colour")||"rgba(255,255,255,0.3)",a=i(t,"duration",600),o=t.getBoundingClientRect(),f=Math.max(o.width,o.height)*2,s=document.createElement("span");s.style.cssText="position:absolute;border-radius:50%;pointer-events:none;width:"+f+"px;height:"+f+"px;left:"+(e.clientX-o.left-f/2)+"px;top:"+(e.clientY-o.top-f/2)+"px;background:"+n+";transform:scale(0);opacity:1;transition:transform "+a+"ms ease,opacity "+a+"ms ease;",t.appendChild(s),requestAnimationFrame(function(){s.style.transform="scale(1)",s.style.opacity="0"}),setTimeout(function(){s.remove()},a)})});var c=window.Domma&&window.Domma.effects;c&&(d.querySelectorAll(".dm-fx-breathe").forEach(function(t){if(!p){var e={};r(t,"amplitude")&&(e.amplitude=i(t,"amplitude",6)),r(t,"duration")&&(e.duration=i(t,"duration",3e3)),r(t,"easing")&&(e.easing=r(t,"easing")),r(t,"stagger")&&(e.stagger=i(t,"stagger",0)),c.breathe(t,e)}}),d.querySelectorAll(".dm-fx-pulse").forEach(function(t){if(!p){var e={};r(t,"scale")&&(e.scale=i(t,"scale",1.05)),r(t,"duration")&&(e.duration=i(t,"duration",2e3)),r(t,"easing")&&(e.easing=r(t,"easing")),c.pulse(t,e)}}),d.querySelectorAll(".dm-fx-scribe").forEach(function(t){var e={};r(t,"speed")&&(e.speed=i(t,"speed",50)),r(t,"delete-speed")&&(e.deleteSpeed=i(t,"delete-speed",30)),r(t,"cursor")&&(e.cursor=v(t,"cursor",!0)),r(t,"cursor-char")&&(e.cursorChar=r(t,"cursor-char")),r(t,"cursor-type")&&(e.cursorType=r(t,"cursor-type")),r(t,"mode")&&(e.mode=r(t,"mode")),r(t,"loop")&&(e.loop=v(t,"loop",!1)),r(t,"loop-delay")&&(e.loopDelay=i(t,"loop-delay",1e3)),r(t,"pause-on-hover")&&(e.pauseOnHover=v(t,"pause-on-hover",!1));var n=r(t,"actions");if(n)try{e.actions=JSON.parse(n)}catch{return}else{var a=t.textContent.trim();if(!a)return;t.textContent="",e.actions=[{render:a}]}c.scribe(t,e)}),d.querySelectorAll(".dm-fx-twinkle").forEach(function(t){var e={};r(t,"count")&&(e.count=i(t,"count",100)),r(t,"shape")&&(e.shape=r(t,"shape")),r(t,"colour")&&(e.colour=r(t,"colour")),r(t,"min-size")&&(e.minSize=i(t,"min-size",1)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",3)),c.twinkle(t,e)}),typeof c.tickerTape=="function"&&d.querySelectorAll(".dm-fx-ticker-tape").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",50)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"sway")&&(e.sway=i(t,"sway",60)),r(t,"rotation-speed")&&(e.rotationSpeed=i(t,"rotation-speed",6)),r(t,"min-width")&&(e.minWidth=i(t,"min-width",5)),r(t,"max-width")&&(e.maxWidth=i(t,"max-width",9)),r(t,"min-height")&&(e.minHeight=i(t,"min-height",12)),r(t,"max-height")&&(e.maxHeight=i(t,"max-height",22)),r(t,"fade-start")&&(e.fadeStart=i(t,"fade-start",.55)),r(t,"burst")&&(e.burst=v(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",150)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?c.tickerTape(null,e):c.tickerTape(t,e)}}),typeof c.butterflies=="function"&&d.querySelectorAll(".dm-fx-butterflies").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",18)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"wander")&&(e.wander=i(t,"wander",1)),r(t,"rise-speed")&&(e.riseSpeed=i(t,"rise-speed",.4)),r(t,"flap-speed")&&(e.flapSpeed=i(t,"flap-speed",1)),r(t,"min-size")&&(e.minSize=i(t,"min-size",14)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",26)),r(t,"two-tone")&&(e.twoTone=v(t,"two-tone",!0)),r(t,"burst")&&(e.burst=v(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",40)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?c.butterflies(null,e):c.butterflies(t,e)}}),typeof c.strobe=="function"&&d.querySelectorAll(".dm-fx-strobe").forEach(function(t){if(p)return;function e(s){var u=r(t,s);if(!u)return null;var h=u.split(",").map(function(m){return m.trim()}).filter(Boolean);return h.length?h:null}var n={};r(t,"preset")&&(n.preset=r(t,"preset")),r(t,"motion")&&(n.motion=r(t,"motion")),r(t,"speed")&&(n.speed=i(t,"speed",1)),r(t,"sweep-arc")&&(n.sweepArc=i(t,"sweep-arc",100)),r(t,"beam-width")&&(n.beamWidth=i(t,"beam-width",16)),r(t,"intensity")&&(n.intensity=i(t,"intensity",.7)),r(t,"flicker")&&(n.flicker=v(t,"flicker",!0)),r(t,"hz")&&(n.hz=i(t,"hz",10));var a=e("origins");a&&(n.origins=a);var o=e("colours");o&&(n.colours=o),r(t,"duration")&&(n.duration=i(t,"duration",null)),r(t,"z-index")&&(n.zIndex=i(t,"z-index",9999));var f=r(t,"mode");f==="page"?c.strobe(null,n):c.strobe(t,n)}));var g=d.querySelectorAll(".dm-fx-celebrate");g.length&&!p&&import("/public/js/celebrations/index.js?v=20260930-festive").then(function(t){var e=t.CelebrationsEffect;g.forEach(function(n){var a=n.getAttribute("data-fx-theme")||"auto",o=n.getAttribute("data-fx-intensity")||"medium",f=n.getAttribute("data-fx-z-index")?parseInt(n.getAttribute("data-fx-z-index"),10):999;if(!(a==="auto"&&(a=e.getCurrentTheme(),!a))){var s={},u=["data-fx-theme","data-fx-intensity","data-fx-z-index"];Array.from(n.attributes).forEach(function(m){m.name.startsWith("data-fx-")&&!u.includes(m.name)&&(s[m.name.slice(8)]=m.value)});var h=new e({theme:a,intensity:o,enabled:!0,zIndex:f,overrides:s});h.init().then(function(){h.start()})}})}).catch(function(){})}).catch(function(){})})();
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Instant calls - /api/calls (services/instantCalls.js)
3
+ *
4
+ * Every route here answers 404 until a Pro Tool with calls (Booking Pro,
5
+ * Contacts Pro, Calendar) has switched instant calls on - a free site has none -
6
+ * and again the moment the last one is disabled.
7
+ *
8
+ * Signed in, `calls` permission:
9
+ * GET /api/calls/status {mailReady, contacts} (calls.create)
10
+ * GET /api/calls/contacts?q= contacts the caller may READ (own + shared), to pick (calls.create)
11
+ * GET /api/calls/contact/:id one contact the caller may read, to fill in (calls.create)
12
+ * POST /api/calls {name, email?, contactId?, notify} -> 201 {id, hostUrl, joinUrl, emailed, emailLimited?, expiresAt}
13
+ * GET /api/calls?contact=<id> the caller's recent calls with that contact (calls.read)
14
+ *
15
+ * Contacts are read through `readableContactIds` (services/hooks.js): the
16
+ * caller's own, plus whatever a sharing plugin (Contacts Pro) lets them read -
17
+ * its rule, not a copy of it. Reading contacts also needs Contacts on and the
18
+ * `contacts.read` permission.
19
+ *
20
+ * Limits on POST: 20 a minute per address (the route), CREATES_PER_MINUTE per
21
+ * user, and EMAILS_PER_ADDRESS_HOUR emails to any one recipient - past that the
22
+ * call is still made, with `emailed: false, emailLimited: true`.
23
+ *
24
+ * The call itself (no sign-in - the link is the credential):
25
+ * /api/calls/room/:id/{join,poll,signal,chat,answer,recording,leave} (plugins/_lib/call/routes.js)
26
+ * join takes {t} (the guest) or {h} (the host); guests knock and the host lets them in.
27
+ */
28
+ import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
29
+ import {getEntry, listEntries} from '../../services/collections.js';
30
+ import {readableContactIds} from '../../services/hooks.js';
31
+ import {getPermissionsFor} from '../../services/roles.js';
32
+ import {getEffectiveRoles} from '../../services/userRoles.js';
33
+ import {isToolEnabled} from '../../services/tools.js';
34
+ import {mountCallRoutes} from '../../../plugins/_lib/call/routes.js';
35
+ import {getBaseUrl} from '../public.js';
36
+ import {
37
+ EMAIL_RE, callMailReady, callPaths, callsOfContact, createCall, getCall, instantCallsEnabled, isOpen,
38
+ mayEmailAddress, mayStartCall, sendCallEmail, whoIsCalling
39
+ } from '../../services/instantCalls.js';
40
+
41
+ const CONTACTS_SLUG = 'contacts-contacts';
42
+
43
+ /**
44
+ * @param {import('fastify').FastifyInstance} fastify
45
+ * @param {{authenticate?: Function, requirePermission?: Function, isEnabled?: () => boolean, rooms?: object}} [opts] - injectable for tests
46
+ */
47
+ export async function callsRoutes(fastify, opts = {}) {
48
+ const authenticate = opts.authenticate || defaultAuthenticate;
49
+ const requirePermission = opts.requirePermission || defaultRequirePermission;
50
+ const isEnabled = opts.isEnabled || instantCallsEnabled;
51
+ const can = (action) => ({preHandler: [authenticate, requirePermission('calls', action)]});
52
+ const uidOf = (request) => String(request.user?.id ?? request.user?.sub ?? '');
53
+ const hostNameOf = (request) => String(request.user?.name || request.user?.email || '').trim();
54
+ const fail = (reply, status, error, extra = {}) => reply.code(status).send({error, ...extra});
55
+ const viewerOf = (request) => ({id: uidOf(request), roles: getEffectiveRoles(request.user || {})});
56
+ /** Contacts on, and a role that may read them. */
57
+ const mayReadContacts = (viewer) => isToolEnabled('contacts')
58
+ && viewer.roles.some(r => getPermissionsFor('contacts', 'read').includes(r));
59
+ const shape = (e) => ({id: e.id, name: String(e.data?.name || ''), email: String(e.data?.email || '')});
60
+
61
+ // No Pro Tool with calls: nothing here exists.
62
+ fastify.addHook('onRequest', async (request, reply) => {
63
+ if (!isEnabled()) return reply.code(404).send({error: 'Not found'});
64
+ });
65
+
66
+ /** One contact the caller may read, or null (Contacts off, not readable, gone). */
67
+ async function readableContact(id, viewer) {
68
+ if (!id || !viewer.id || !mayReadContacts(viewer)) return null;
69
+ const e = await getEntry(CONTACTS_SLUG, String(id)).catch(() => null);
70
+ if (!e || !(await readableContactIds([e], viewer)).has(e.id)) return null;
71
+ return {...shape(e), mine: e.data?.userId === viewer.id};
72
+ }
73
+
74
+ fastify.get('/status', can('create'), async (request, reply) => {
75
+ return reply.send({mailReady: callMailReady(), contacts: mayReadContacts(viewerOf(request))});
76
+ });
77
+
78
+ fastify.get('/contacts', can('create'), async (request, reply) => {
79
+ const viewer = viewerOf(request);
80
+ if (!mayReadContacts(viewer)) return fail(reply, 403, 'Contacts are not available to you.');
81
+ const q = String(request.query?.q || '').trim().toLowerCase().slice(0, 100);
82
+ if (q.length < 2) return reply.send([]);
83
+ const {entries} = await listEntries(CONTACTS_SLUG, {limit: 100000}).catch(() => ({entries: []}));
84
+ const hits = (entries || []).filter(e => [e.data?.name, e.data?.email, e.data?.phone]
85
+ .some(v => String(v || '').toLowerCase().includes(q)));
86
+ const ok = await readableContactIds(hits, viewer);
87
+ const out = hits.filter(e => ok.has(e.id)).map(e => ({...shape(e), mine: e.data?.userId === viewer.id}))
88
+ .sort((a, b) => Number(b.mine) - Number(a.mine) || a.name.localeCompare(b.name)).slice(0, 20);
89
+ return reply.send(out);
90
+ });
91
+
92
+ fastify.get('/contact/:id', can('create'), async (request, reply) => {
93
+ const c = await readableContact(String(request.params.id), viewerOf(request));
94
+ if (!c) return fail(reply, 404, 'That contact could not be found.');
95
+ return reply.send(c);
96
+ });
97
+
98
+ fastify.post('/', {...can('create'), config: {rateLimit: {max: 20, timeWindow: '1 minute'}}}, async (request, reply) => {
99
+ const body = request.body || {};
100
+ const name = String(body.name || '').trim().slice(0, 120);
101
+ const email = String(body.email || '').trim().toLowerCase().slice(0, 200);
102
+ if (email && !EMAIL_RE.test(email)) return fail(reply, 400, 'That email address does not look right.', {errors: {email: 'That email address does not look right.'}});
103
+ if (body.notify === true && !email) return fail(reply, 400, 'An email address, to send them the link.', {errors: {email: 'An email address, to send them the link.'}});
104
+ const viewer = viewerOf(request);
105
+ if (!mayStartCall(viewer.id)) return fail(reply, 429, 'That is a lot of calls in a minute - wait a moment and try again.');
106
+ const contact = body.contactId ? await readableContact(String(body.contactId), viewer) : null;
107
+ const rec = await createCall({name, email, contactId: contact?.id || '', hostId: viewer.id, hostName: hostNameOf(request)});
108
+ const paths = callPaths(rec);
109
+ const joinUrl = `${getBaseUrl(request)}${paths.join}`;
110
+ let emailed = false;
111
+ let emailLimited = false;
112
+ if (body.notify === true && email) {
113
+ if (mayEmailAddress(email)) emailed = await sendCallEmail({to: email, name, hostName: rec.hostName, joinUrl}, {log: request.log});
114
+ else emailLimited = true;
115
+ }
116
+ return reply.code(201).send({id: rec.id, hostUrl: paths.host, joinUrl, emailed: emailed === true,
117
+ ...(emailLimited ? {emailLimited: true} : {}), expiresAt: rec.expiresAt});
118
+ });
119
+
120
+ fastify.get('/', can('read'), async (request, reply) => {
121
+ const contact = String(request.query?.contact || '');
122
+ if (!contact) return fail(reply, 400, 'contact is required');
123
+ const now = Date.now();
124
+ const list = (await callsOfContact(contact, uidOf(request))).slice(0, 20)
125
+ .map(c => ({id: c.id, name: c.name, createdAt: c.createdAt, expiresAt: c.expiresAt, open: isOpen(c, now)}));
126
+ return reply.send(list);
127
+ });
128
+
129
+ // The call engine: a room per call, guests knock, the host lets them in, open createdAt..expiresAt.
130
+ mountCallRoutes(fastify, {
131
+ prefix: '/room', ns: 'instant', ...(opts.rooms ? {rooms: opts.rooms} : {}),
132
+ resolve: async (request, room, body) => whoIsCalling(await getCall(String(room)), {t: body?.t, h: body?.h})
133
+ });
134
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * An instant call's page (services/instantCalls.js), in the site's own shell:
3
+ *
4
+ * /_call/<id>?t=<guest token> the person called
5
+ * /_call/<id>?h=<host key> the host
6
+ *
7
+ * Under `/_call`, like `/_preview`. Answered from an onRequest hook, and ONLY
8
+ * while instant calls are on: with no Pro Tool with calls the request goes on
9
+ * to the normal page handling, so the path is never claimed on a free site.
10
+ * The link is checked here only to choose between the call and a short note;
11
+ * the call's join checks it again, and the time.
12
+ *
13
+ * The credential is in the URL, so: no referrer, no caching, no indexing, and
14
+ * the page is rendered WITHOUT the site's and plugins' injected head/body
15
+ * snippets or the analytics beacon (renderBlogPage `injection: false`) - no
16
+ * third-party script sees `?h=`. call.js then takes the credentials out of the
17
+ * address bar (keeping them in sessionStorage for this tab), and a reload of
18
+ * the bare address comes back to them through the small script on the "not
19
+ * valid" page (`recoverScript`).
20
+ *
21
+ * @module routes/call-public
22
+ */
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import {fileURLToPath} from 'node:url';
26
+ import {renderBlogPage} from '../services/renderer.js';
27
+ import {getBaseUrl} from './public.js';
28
+ import {CALL_PATH, ROOM_API, getCall, instantCallsEnabled, whoIsCalling} from '../services/instantCalls.js';
29
+
30
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
31
+ const TEMPLATE = path.join(HERE, '..', 'templates', 'call.html');
32
+ const ASSET_V = (() => {
33
+ try { return JSON.parse(fs.readFileSync(path.join(HERE, '..', '..', 'package.json'), 'utf8')).version || '1'; } catch { return '1'; }
34
+ })();
35
+
36
+ export const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
37
+
38
+ /**
39
+ * The page's body and status: the call (200), "not valid" (404) or "ended" (410).
40
+ *
41
+ * @param {object|null} rec
42
+ * @param {object|null} who - whoIsCalling(rec, cred)
43
+ * @param {{t?: string, h?: string}} cred
44
+ */
45
+ /**
46
+ * On the "not valid" page of a bare `/_call/<id>` (no credentials in the
47
+ * address): this tab's call page stripped them (call.js) - go back to it with them.
48
+ * A wrong credential always has a query string, so this cannot loop.
49
+ */
50
+ export const recoverScript = '<script>(function(){try{if(location.search)return;'
51
+ + 'var s=sessionStorage.getItem("dm-call:"+location.pathname);'
52
+ + 'if(s&&s.charAt(0)==="?")location.replace(location.pathname+s);}catch(e){}})();</script>';
53
+
54
+ export function callPage(rec, who, cred, now = Date.now()) {
55
+ if (!rec || !who) {
56
+ const bare = !cred?.t && !cred?.h;
57
+ return {status: 404, title: 'Call', body: '<h1>This call link is not valid</h1><p>Ask the person who sent it for a new one.</p>'
58
+ + (bare ? recoverScript : '')};
59
+ }
60
+ if (!(Date.parse(rec.expiresAt) > now)) {
61
+ return {status: 410, title: 'Call', body: '<h1>This call has ended</h1><p>Ask the person who sent the link for a new one.</p>'};
62
+ }
63
+ const cfg = {api: ROOM_API, room: rec.id, cred, title: who.title, askName: !who.host && !rec.name, recordingAllowed: false, closesAt: rec.expiresAt};
64
+ return {
65
+ status: 200,
66
+ title: who.title,
67
+ body: `<link rel="stylesheet" href="/plugins/_lib/public/call/call.css?v=${esc(ASSET_V)}">`
68
+ + `<div data-dm-call='${esc(JSON.stringify(cfg))}'><noscript><p>The call needs JavaScript switched on.</p></noscript></div>`
69
+ + `<script type="module" src="/plugins/_lib/public/call/call.js?v=${esc(ASSET_V)}"></script>`
70
+ };
71
+ }
72
+
73
+ /**
74
+ * @param {import('fastify').FastifyInstance} fastify
75
+ * @param {{isEnabled?: () => boolean}} [opts]
76
+ */
77
+ export async function callPublicRoutes(fastify, opts = {}) {
78
+ const isEnabled = opts.isEnabled || instantCallsEnabled;
79
+ const PREFIX = `${CALL_PATH}/`;
80
+
81
+ fastify.addHook('onRequest', async (request, reply) => {
82
+ if (request.method !== 'GET' && request.method !== 'HEAD') return;
83
+ const [pathname, search = ''] = String(request.url || '').split('?');
84
+ if (!pathname.startsWith(PREFIX) || !isEnabled()) return;
85
+ let id = '';
86
+ try { id = decodeURIComponent(pathname.slice(PREFIX.length)).slice(0, 64); } catch { return; }
87
+ if (!id || id.includes('/')) return;
88
+ reply.header('referrer-policy', 'no-referrer');
89
+ reply.header('cache-control', 'no-store, private');
90
+ reply.header('x-robots-tag', 'noindex, nofollow');
91
+ const q = new URLSearchParams(search);
92
+ const cred = q.get('h') ? {h: q.get('h').slice(0, 128)} : {t: String(q.get('t') || '').slice(0, 128)};
93
+ const rec = await getCall(id);
94
+ const page = callPage(rec, whoIsCalling(rec, cred), cred);
95
+ const html = await renderBlogPage(TEMPLATE, {body: page.body}, {
96
+ title: page.title, urlPath: `${CALL_PATH}/${encodeURIComponent(id)}`, noindex: true, baseUrl: getBaseUrl(request),
97
+ injection: false
98
+ });
99
+ return reply.code(page.status).type('text/html; charset=utf-8').send(html);
100
+ });
101
+ }
102
+
103
+ // The onRequest hook must see every request, not only routes registered here (it registers none).
104
+ callPublicRoutes[Symbol.for('skip-override')] = true;
package/server/server.js CHANGED
@@ -579,6 +579,12 @@ await app.register(toolGate('seo', seoRoutes), {prefix: '/api/seo'});
579
579
  await app.register(toolsRoutes, {prefix: '/api'});
580
580
  // What plugins show beside a contact or a note (Calendar's Events) - 0.79.
581
581
  await app.register(relatedRoutes, {prefix: '/api/related'});
582
+ // Instant calls ("Call Sam now…") - core code, switched on only by a Pro Tool with calls
583
+ // (hooks.enableInstantCalls, services/instantCalls.js); 404 on every route until then.
584
+ {
585
+ const {callsRoutes} = await import('./routes/api/calls.js');
586
+ await app.register(callsRoutes, {prefix: '/api/calls'});
587
+ }
582
588
 
583
589
  // ---------------------------------------------------------------------------
584
590
  // CMS Plugins (server-side Fastify plugins from plugins/ directory)
@@ -702,6 +708,9 @@ for (const [name, plugin] of Object.entries(getLoadedPlugins())) {
702
708
  // out-prioritises the catch-all; must be registered before it.
703
709
  const { docsPublicRoutes } = await import('./routes/docs-public.js');
704
710
  await app.register(docsPublicRoutes);
711
+ // An instant call's page, /_call/<id> (a reserved prefix, like /_preview) - before the catch-all.
712
+ const { callPublicRoutes } = await import('./routes/call-public.js');
713
+ await app.register(callPublicRoutes);
705
714
 
706
715
  const { publicRoutes } = await import('./routes/public.js');
707
716
  await app.register(publicRoutes);
@@ -299,9 +299,12 @@ const _relatedProviders = new Map(); // kind -> Map(id -> provider)
299
299
  * `actions` are shown in the record's menu, `{id}` in a url replaced by the
300
300
  * record's id: `[{label, icon?, url, permission?}]`. `permission`
301
301
  * (`{resource, action}`) shows the action only to roles that hold it.
302
+ * `when` (optional, `() => boolean`) is asked on every request: a provider that
303
+ * answers false is left out for now (its actions and its list) - core's instant
304
+ * calls stand aside that way while an older Booking Pro registers its own.
302
305
  *
303
306
  * @param {'contact'|'note'|'message'} kind
304
- * @param {{id: string, label: string, icon?: string, list: Function, actions?: object[], owner?: string}} provider
307
+ * @param {{id: string, label: string, icon?: string, list: Function, actions?: object[], owner?: string, when?: Function}} provider
305
308
  * @returns {boolean}
306
309
  */
307
310
  export function registerRelatedItems(kind, provider = {}) {
@@ -326,11 +329,17 @@ export function registerRelatedItems(kind, provider = {}) {
326
329
  icon: typeof provider.icon === 'string' ? provider.icon : 'link',
327
330
  owner: typeof provider.owner === 'string' ? provider.owner : 'core',
328
331
  actions,
329
- list: typeof list === 'function' ? list : () => []
332
+ list: typeof list === 'function' ? list : () => [],
333
+ ...(typeof provider.when === 'function' ? {when: provider.when} : {})
330
334
  });
331
335
  return true;
332
336
  }
333
337
 
338
+ /** Is a provider with this id registered for this kind (whatever its `when` says)? */
339
+ export function hasRelatedProvider(kind, id) {
340
+ return Boolean(_relatedProviders.get(kind)?.has(id));
341
+ }
342
+
334
343
  /** @param {string} kind @param {string} id */
335
344
  export function unregisterRelatedItems(kind, id) {
336
345
  _relatedProviders.get(kind)?.delete(id);
@@ -338,7 +347,61 @@ export function unregisterRelatedItems(kind, id) {
338
347
 
339
348
  /** The providers for one kind of record. */
340
349
  export function getRelatedProviders(kind) {
341
- return [...(_relatedProviders.get(kind)?.values() ?? [])];
350
+ return [...(_relatedProviders.get(kind)?.values() ?? [])].filter((p) => {
351
+ try { return !p.when || p.when() !== false; } catch { return true; }
352
+ });
353
+ }
354
+
355
+ // ---------------------------------------------------------------------------
356
+ // 7b. Contact read access - who may read a contact that is not their own
357
+ // ---------------------------------------------------------------------------
358
+ //
359
+ // Core Contacts are per user: only the owner reads one. A plugin that shares
360
+ // contacts (Contacts Pro) registers the rule it enforces itself, so core code
361
+ // that takes a contact id (instant calls) accepts exactly the contacts the
362
+ // caller may read, without reimplementing the plugin's rule.
363
+
364
+ const _contactAccess = new Map(); // owner -> fn(entries, viewer) => Promise<Iterable<id>>
365
+
366
+ /**
367
+ * @param {string} owner - the plugin
368
+ * @param {(entries: object[], viewer: {id: string, roles: string[]}) => Promise<Iterable<string>>} fn
369
+ * of the collection entries given (contacts-contacts, not the viewer's own), the ids the viewer may READ
370
+ */
371
+ export function registerContactAccess(owner, fn) {
372
+ if (typeof owner !== 'string' || !owner || typeof fn !== 'function') return false;
373
+ _contactAccess.set(owner, fn);
374
+ return true;
375
+ }
376
+
377
+ /** @param {string} owner */
378
+ export function unregisterContactAccess(owner) {
379
+ _contactAccess.delete(owner);
380
+ }
381
+
382
+ /**
383
+ * Which of these contact entries may the viewer read? Their own, plus whatever
384
+ * a registered rule allows. A rule that throws allows nothing.
385
+ *
386
+ * @param {object[]} entries - contacts-contacts entries
387
+ * @param {{id: string, roles?: string[]}} viewer
388
+ * @returns {Promise<Set<string>>} entry ids
389
+ */
390
+ export async function readableContactIds(entries, viewer) {
391
+ const out = new Set();
392
+ if (!viewer?.id) return out;
393
+ const others = [];
394
+ for (const e of entries || []) {
395
+ if (e?.data?.userId === viewer.id) out.add(e.id);
396
+ else if (e) others.push(e);
397
+ }
398
+ if (!others.length) return out;
399
+ for (const fn of _contactAccess.values()) {
400
+ try {
401
+ for (const id of (await fn(others, {id: viewer.id, roles: viewer.roles || []})) || []) out.add(String(id));
402
+ } catch { /* a failing rule grants nothing */ }
403
+ }
404
+ return out;
342
405
  }
343
406
 
344
407
  // ---------------------------------------------------------------------------