domma-cms 0.115.5 → 0.116.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.
- package/admin/js/lib/effect-defs.js +1 -1
- package/package.json +1 -1
- package/plugins/free-tier.lock.json +16 -16
- package/plugins/support/CLAUDE.md +19 -0
- package/plugins/support/admin/css/index.css +4 -0
- package/plugins/support/admin/lib/kit.js +60 -11
- package/plugins/support/admin/views/desk.js +158 -74
- package/plugins/support/admin/views/inbox.js +146 -56
- package/plugins/support/admin/views/support.js +1 -1
- package/plugins/support/collections/support-tickets.json +1 -0
- package/plugins/support/lib/receiver.js +2 -1
- package/plugins/support/lib/sender.js +68 -4
- package/plugins/support/lib/shape.js +10 -1
- package/plugins/support/plugin.js +38 -6
- package/plugins/support/plugin.json +2 -2
- package/plugins/support/tests/api.test.js +94 -0
- package/plugins/support/tests/shape.test.js +68 -1
- package/public/js/celebrations/character-catalog.mjs +1 -0
- package/public/js/celebrations/characters.js +1 -0
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/mooning-antics.js +1 -0
- package/public/js/celebrations/themes/mooning-drawing.js +1 -0
- package/public/js/celebrations/themes/st-patricks.js +1 -1
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/server/services/markdown.js +18 -2
- package/server/templates/page.html +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "support",
|
|
3
3
|
"displayName": "Support",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"tier": "free",
|
|
6
|
-
"description": "Get help from Domma straight from your admin: our email, phone and hours always to hand, and a conversation with the Domma team for anything you need - urgent requests first.",
|
|
6
|
+
"description": "Get help from Domma straight from your admin: our email, phone and hours always to hand, and a conversation with the Domma team for anything you need - urgent requests first. Right-click a request to archive, resolve or remove it.",
|
|
7
7
|
"author": "Domma CMS",
|
|
8
8
|
"date": "2026-10-04",
|
|
9
9
|
"icon": "headphones",
|
|
@@ -285,3 +285,97 @@ test('replies go to the address a site was bought with; a hand-made site falls b
|
|
|
285
285
|
await call(manager, 'POST', `/api/plugins/support/tickets/${tb.id}/reply`, {body: 'hi Bob'});
|
|
286
286
|
assert.ok(mails.some(m => m.to === 'bob@beta.test'));
|
|
287
287
|
});
|
|
288
|
+
|
|
289
|
+
test('the inbox archive: out of the badge, back when the customer writes; the site never knows', async () => {
|
|
290
|
+
const acme = await site({token: 'tok-acme'});
|
|
291
|
+
const {body: s} = await call(acme, 'POST', '/tickets', {subject: 'Archive me', body: 'x', priority: 'urgent'});
|
|
292
|
+
const t = (await call(manager, 'GET', '/api/plugins/support/tickets')).body.tickets.find(x => x.ref === s.ref);
|
|
293
|
+
const before = (await call(manager, 'GET', '/api/plugins/support/count')).body;
|
|
294
|
+
const put = await call(manager, 'PUT', `/api/plugins/support/tickets/${t.id}`, {archived: true});
|
|
295
|
+
assert.equal(put.status, 200);
|
|
296
|
+
assert.equal(put.body.archived, true);
|
|
297
|
+
assert.equal(put.body.history.at(-1).what, 'Archived');
|
|
298
|
+
const after = (await call(manager, 'GET', '/api/plugins/support/count')).body;
|
|
299
|
+
assert.equal((after.count ?? 0) < (before.count ?? 0) || after.inventory === true, true, 'an archived request is not waiting');
|
|
300
|
+
assert.ok(!(after.details.items || []).some(i => i.text === 'Archive me'));
|
|
301
|
+
assert.equal((await call(manager, 'PUT', `/api/plugins/support/tickets/${t.id}`, {archived: 'no'})).status, 400);
|
|
302
|
+
// The site sees nothing of Domma's filing.
|
|
303
|
+
const mine = (await call(acme, 'GET', '/tickets')).body.tickets.find(x => x.ref === s.ref);
|
|
304
|
+
assert.equal(mine.archived, false);
|
|
305
|
+
// The customer writes again: it is back in the inbox.
|
|
306
|
+
await call(acme, 'POST', `/tickets/${s.ref}/messages`, {body: 'still there?'});
|
|
307
|
+
assert.equal((await call(manager, 'GET', `/api/plugins/support/tickets/${t.id}`)).body.archived, false);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('a site archives and removes its own requests; Domma keeps them, and a reply brings them back', async () => {
|
|
311
|
+
const acme = await site({token: 'tok-acme'});
|
|
312
|
+
const a = (await call(acme, 'POST', '/tickets', {subject: 'Keep me', body: 'x'})).body;
|
|
313
|
+
const b = (await call(acme, 'POST', '/tickets', {subject: 'Lose me', body: 'y'})).body;
|
|
314
|
+
// A sync: the site knows both. Earlier tests' replies are read, so the badge counts open requests.
|
|
315
|
+
for (const t of (await call(acme, 'GET', '/tickets')).body.tickets.filter(x => x.unread)) {
|
|
316
|
+
await call(acme, 'PUT', `/tickets/${t.ref}/seen`, {at: t.messages.filter(m => m.from === 'domma').at(-1).at});
|
|
317
|
+
}
|
|
318
|
+
assert.equal((await call(acme, 'GET', '/count')).body.inventory, true);
|
|
319
|
+
const openBefore = (await call(acme, 'GET', '/count')).body.count;
|
|
320
|
+
assert.equal((await call(acme, 'PUT', `/tickets/${a.ref}/shelf`, {state: 'archived'})).body.state, 'archived');
|
|
321
|
+
assert.equal((await call(acme, 'PUT', `/tickets/${b.ref}/shelf`, {state: 'removed'})).body.state, 'removed');
|
|
322
|
+
assert.equal((await call(acme, 'PUT', `/tickets/${b.ref}/shelf`, {state: 'binned'})).status, 400);
|
|
323
|
+
assert.equal((await call(acme, 'PUT', '/tickets/nope/shelf', {state: 'archived'})).status, 404);
|
|
324
|
+
let list = (await call(acme, 'GET', '/tickets')).body.tickets;
|
|
325
|
+
assert.equal(list.find(x => x.ref === a.ref).archived, true);
|
|
326
|
+
assert.ok(!list.some(x => x.ref === b.ref), 'removed from the list');
|
|
327
|
+
// Neither counts on the badge.
|
|
328
|
+
assert.equal((await call(acme, 'GET', '/count')).body.count, openBefore - 2);
|
|
329
|
+
// Domma still has both.
|
|
330
|
+
const inbox = (await call(manager, 'GET', '/api/plugins/support/tickets')).body.tickets;
|
|
331
|
+
const tb = inbox.find(x => x.ref === b.ref);
|
|
332
|
+
assert.ok(tb && inbox.some(x => x.ref === a.ref));
|
|
333
|
+
// Domma replies to the removed one: it comes back, unread.
|
|
334
|
+
await call(manager, 'POST', `/api/plugins/support/tickets/${tb.id}/reply`, {body: 'We fixed it.'});
|
|
335
|
+
list = (await call(acme, 'GET', '/tickets')).body.tickets;
|
|
336
|
+
const back = list.find(x => x.ref === b.ref);
|
|
337
|
+
assert.ok(back, 'a new reply brings it back');
|
|
338
|
+
assert.equal(back.archived, false);
|
|
339
|
+
assert.equal(back.unread, true);
|
|
340
|
+
// Put back by hand.
|
|
341
|
+
await call(acme, 'PUT', `/tickets/${a.ref}/shelf`, {state: null});
|
|
342
|
+
assert.equal((await call(acme, 'GET', '/tickets')).body.tickets.find(x => x.ref === a.ref).archived, false);
|
|
343
|
+
// Writing to an archived one brings it back too.
|
|
344
|
+
await call(acme, 'PUT', `/tickets/${a.ref}/shelf`, {state: 'archived'});
|
|
345
|
+
await call(acme, 'POST', `/tickets/${a.ref}/messages`, {body: 'one more thing'});
|
|
346
|
+
assert.equal((await call(acme, 'GET', '/tickets')).body.tickets.find(x => x.ref === a.ref).archived, false);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('removing a request that never reached Domma cancels it - it is never sent', async () => {
|
|
350
|
+
const acme = await site({token: 'tok-acme'});
|
|
351
|
+
managerUp = false;
|
|
352
|
+
let s;
|
|
353
|
+
try {
|
|
354
|
+
s = (await call(acme, 'POST', '/tickets', {subject: 'Never mind', body: 'x'})).body;
|
|
355
|
+
await call(acme, 'POST', `/tickets/${s.ref}/messages`, {body: 'follow-up'});
|
|
356
|
+
assert.equal(s.state, 'waiting');
|
|
357
|
+
const r = await call(acme, 'PUT', `/tickets/${s.ref}/shelf`, {state: 'removed'});
|
|
358
|
+
assert.equal(r.body.state, 'discarded');
|
|
359
|
+
} finally { managerUp = true; }
|
|
360
|
+
const list = (await call(acme, 'GET', '/tickets')).body.tickets;
|
|
361
|
+
assert.ok(!list.some(x => x.ref === s.ref));
|
|
362
|
+
const inbox = (await call(manager, 'GET', '/api/plugins/support/tickets')).body.tickets;
|
|
363
|
+
assert.ok(!inbox.some(x => x.ref === s.ref), 'Domma never got it');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('review: archiving measures against the reply the customer SAW - a newer one is not put away unread', async () => {
|
|
367
|
+
const acme = await site({token: 'tok-acme'});
|
|
368
|
+
const s = (await call(acme, 'POST', '/tickets', {subject: 'Seen vs newer', body: 'x'})).body;
|
|
369
|
+
const t = (await call(manager, 'GET', '/api/plugins/support/tickets')).body.tickets.find(x => x.ref === s.ref);
|
|
370
|
+
await call(manager, 'POST', `/api/plugins/support/tickets/${t.id}/reply`, {body: 'first'});
|
|
371
|
+
const shown = (await call(acme, 'GET', '/tickets')).body.tickets.find(x => x.ref === s.ref);
|
|
372
|
+
const seenAt = shown.messages.at(-1).at;
|
|
373
|
+
await new Promise(r => setTimeout(r, 5));
|
|
374
|
+
await call(manager, 'POST', `/api/plugins/support/tickets/${t.id}/reply`, {body: 'second - not on their screen'});
|
|
375
|
+
await call(acme, 'GET', '/count'); // a background sync hears of it
|
|
376
|
+
await call(acme, 'GET', '/tickets');
|
|
377
|
+
await call(acme, 'PUT', `/tickets/${s.ref}/shelf`, {state: 'archived', at: seenAt});
|
|
378
|
+
const now = (await call(acme, 'GET', '/tickets')).body.tickets.find(x => x.ref === s.ref);
|
|
379
|
+
assert.equal(now.archived, false, 'the newer reply keeps it in the list');
|
|
380
|
+
assert.equal(now.unread, true);
|
|
381
|
+
});
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
addTurn, buildMessagePayload, buildTicketPayload, cleanRequest, cleanUpdate, contactCard, createThrottle, lastDommaAt,
|
|
6
6
|
receivedTicket, senderView, telHref, toCustomerEmail, toDommaEmail
|
|
7
7
|
} from '../lib/shape.js';
|
|
8
|
-
import {mergeTickets} from '../lib/sender.js';
|
|
8
|
+
import {applyShelf, createOutbox, flush, mergeTickets} from '../lib/sender.js';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
9
12
|
|
|
10
13
|
const T0 = '2026-10-04T10:00:00.000Z';
|
|
11
14
|
const T1 = '2026-10-04T11:00:00.000Z';
|
|
@@ -120,3 +123,67 @@ test('the site\'s list: the manager\'s view, plus what is still waiting here', (
|
|
|
120
123
|
assert.equal(offline[0].stale, true);
|
|
121
124
|
assert.equal(offline[0].unread, true);
|
|
122
125
|
});
|
|
126
|
+
|
|
127
|
+
test('the inbox archive: a yes/no, and a customer writing again takes a request out of it', () => {
|
|
128
|
+
assert.deepEqual(cleanUpdate({archived: true}), {patch: {archived: true}});
|
|
129
|
+
assert.deepEqual(cleanUpdate({archived: false}), {patch: {archived: false}});
|
|
130
|
+
assert.equal(cleanUpdate({archived: 'yes'}).error, 'Archived is yes or no.');
|
|
131
|
+
const {data} = receivedTicket({ref: 'r1', subject: 'S', body: 'B'}, 'acme', T0);
|
|
132
|
+
const archived = {...data, archived: true};
|
|
133
|
+
// Domma writing to it leaves it where Domma put it.
|
|
134
|
+
assert.equal(addTurn(archived, {from: 'domma', name: 'D', body: 'hi', id: 'd1', at: T1}).data.archived, true);
|
|
135
|
+
// The customer closing it does too.
|
|
136
|
+
assert.equal(addTurn(archived, {from: 'customer', name: 'Ann', id: 'c0', at: T1, close: true}).data.archived, true);
|
|
137
|
+
const back = addTurn(archived, {from: 'customer', name: 'Ann', body: 'still broken', id: 'c1', at: T2}).data;
|
|
138
|
+
assert.equal(back.archived, false);
|
|
139
|
+
assert.equal(back.status, 'open');
|
|
140
|
+
assert.ok(back.history.some(h => h.what === 'Back from the archive'));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('the site shelf: removed ones go, archived ones are marked, a new Domma reply brings either back', () => {
|
|
144
|
+
const t = (ref, dommaAt) => ({ref, subject: ref, messages: dommaAt ? [{from: 'customer', at: T0}, {from: 'domma', at: dommaAt}] : [{from: 'customer', at: T0}]});
|
|
145
|
+
const list = [t('a', T1), t('b', T1), t('c', ''), t('d', T2)];
|
|
146
|
+
const shelf = {
|
|
147
|
+
a: {state: 'archived', dommaAt: T1},
|
|
148
|
+
b: {state: 'removed', dommaAt: T1},
|
|
149
|
+
c: {state: 'removed', dommaAt: ''},
|
|
150
|
+
d: {state: 'archived', dommaAt: T1} // Domma has written since (T2)
|
|
151
|
+
};
|
|
152
|
+
const out = applyShelf(list, shelf);
|
|
153
|
+
assert.deepEqual(out.map(x => [x.ref, x.archived]), [['a', true], ['d', false]]);
|
|
154
|
+
// Nothing shelved: all there, none archived.
|
|
155
|
+
assert.deepEqual(applyShelf(list, {}).map(x => x.archived), [false, false, false, false]);
|
|
156
|
+
// An offline copy has no messages to compare - the shelf is trusted.
|
|
157
|
+
assert.deepEqual(applyShelf([{ref: 'a', stale: true, messages: []}], shelf).map(x => x.archived), [true]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('review: a request removed while a send is in flight is not half-deleted, and a cancelled item is never sent', async () => {
|
|
161
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'support-outbox-'));
|
|
162
|
+
try {
|
|
163
|
+
const outbox = createOutbox(dir);
|
|
164
|
+
outbox.add('ticket', {ref: 'T1', subject: 's', body: 'b'});
|
|
165
|
+
outbox.add('message', {ticketRef: 'T1', ref: 'M1', body: 'more'}, 'T1');
|
|
166
|
+
outbox.add('ticket', {ref: 'T2', subject: 's2', body: 'b2'});
|
|
167
|
+
const posted = [];
|
|
168
|
+
let release;
|
|
169
|
+
const gate = new Promise(r => { release = r; });
|
|
170
|
+
const api = {
|
|
171
|
+
open: async (p) => { posted.push(p.ref); if (p.ref === 'T1') await gate; return {ok: true}; },
|
|
172
|
+
message: async (p) => { posted.push(p.ref); return {ok: true}; }
|
|
173
|
+
};
|
|
174
|
+
const run = flush(outbox, api);
|
|
175
|
+
await new Promise(r => setImmediate(r));
|
|
176
|
+
// T1 is on the wire: discarding it now would be a promise we cannot keep.
|
|
177
|
+
assert.equal(outbox.discard('T1'), false);
|
|
178
|
+
assert.ok(outbox.all().some(i => i.ref === 'T1'));
|
|
179
|
+
release();
|
|
180
|
+
await run;
|
|
181
|
+
assert.deepEqual(posted, ['T1', 'M1', 'T2']);
|
|
182
|
+
// Nothing in flight: a waiting request is cancelled, follow-ups and all.
|
|
183
|
+
outbox.add('ticket', {ref: 'T3', subject: 's3', body: 'b3'});
|
|
184
|
+
outbox.add('message', {ticketRef: 'T3', ref: 'M3', body: 'x'}, 'T3');
|
|
185
|
+
assert.equal(outbox.discard('T3'), true);
|
|
186
|
+
await flush(outbox, api);
|
|
187
|
+
assert.ok(!posted.includes('T3') && !posted.includes('M3'));
|
|
188
|
+
} finally { fs.rmSync(dir, {recursive: true, force: true}); }
|
|
189
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CHARACTER_SIZES=Object.freeze({small:96,medium:160,large:240,xlarge:360}),CHARACTERS=Object.freeze({"mooning-leprechaun":{label:"Mooning leprechaun",module:"mooning",draw:"drawMooningLeprechaun",bounds:[-.9,-1.55,.9,1.5]},"mooning-elf":{label:"Mooning elf",module:"mooning",draw:"drawMooningElf",bounds:[-.9,-1.55,.9,1.5]},leprechaun:{label:"Leprechaun",module:"characters",draw:"drawLeprechaun",bounds:[-.7,-1.55,.7,1.6]},elf:{label:"Elf",module:"festive",draw:"drawElf",bounds:[-.8,-1.55,.85,1.2]},"peeing-elf":{label:"Peeing elf",module:"festive",draw:"drawElf",bounds:[-.8,-1.55,1.6,1.2]},"thieving-elf":{label:"Thieving elf",module:"festive",draw:"drawElf",bounds:[-.8,-1.55,1.1,1.2]},bagpiper:{label:"Bagpiper",module:"characters",draw:"drawBagpiper",bounds:[-.7,-1.5,.7,1.6]},knight:{label:"Knight",module:"characters",draw:"drawKnight",bounds:[-1,-1.5,.8,1.6]},dragon:{label:"Dragon",module:"characters",draw:"drawDragon",bounds:[-2,-1.5,1.8,.9]},"welsh-dragon":{label:"Welsh dragon",module:"characters",draw:"drawWelshDragon",bounds:[-2,-1.5,1.8,.9]},cupid:{label:"Cupid",module:"characters",draw:"drawCupid",bounds:[-1.4,-1.25,1.9,1.25]},robin:{label:"Robin",module:"festive",draw:"drawRobin",bounds:[-1.35,-1.5,1.1,1.25]},snowman:{label:"Snowman",module:"festive",draw:"drawSnowman",bounds:[-2,-1.5,2,2.6]}});export function resolveCharacterSize(e="medium"){const a=String(e).toLowerCase(),n=Object.hasOwn(CHARACTER_SIZES,a)?CHARACTER_SIZES[a]:null;if(n)return n;const o=String(e).trim(),r=/^\d+(?:\.\d+)?(?:px)?$/i.test(o)?Number(o.replace(/px$/i,"")):NaN;return Number.isFinite(r)&&r>0?Math.min(1024,Math.max(16,r)):CHARACTER_SIZES.medium}export function getCharacters(){return Object.entries(CHARACTERS).map(([e,{label:a}])=>({name:e,label:a}))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CHARACTERS as x,resolveCharacterSize as k}from"./character-catalog.mjs";export{CHARACTERS,CHARACTER_SIZES,getCharacters,resolveCharacterSize}from"./character-catalog.mjs";const z={mooning:()=>import("./themes/mooning-drawing.js"),characters:()=>import("./themes/character-drawing.js"),festive:()=>import("./themes/christmas-festive.js")};export async function mountCharacter(g,{character:n="elf",size:A="medium",animate:M=!0,facing:R="right",label:S}={}){const s=Object.hasOwn(x,n)?x[n]:null;if(!s)throw new RangeError(`Unknown celebration character: ${n}`);const b=typeof g=="string"?document.querySelector(g):g;if(!b?.appendChild)throw new TypeError("A character needs a container element.");const T=(await z[s.module]())[s.draw],i=k(A),[C,d,L,m]=s.bounds,r=Math.ceil(i*(L-C)/(m-d)),e=document.createElement("canvas");e.setAttribute("role","img"),e.setAttribute("aria-label",S||s.label),e.style.cssText=`display:block;width:${r}px;height:auto;max-width:100%;aspect-ratio:${r}/${i}`;const o=e.getContext("2d");if(!o)throw new Error("Canvas drawing is unavailable.");b.appendChild(e);const E={type:n,x:-C*i/(m-d),y:-d*i/(m-d),size:i/(m-d),opacity:1,vx:1,state:n==="peeing-elf"?"peeing":n==="thieving-elf"?"escaping":n==="robin"?"sitting":"mooning",time:0},w=window.matchMedia?.("(prefers-reduced-motion: reduce)");let h=M!==!1,a=null,f=0,u=null,l=!1;function v(){const t=Math.min(3,window.devicePixelRatio||1);(e.width!==Math.round(r*t)||e.height!==Math.round(i*t))&&(e.width=Math.round(r*t),e.height=Math.round(i*t)),o.setTransform(t,0,0,t,0,0),o.clearRect(0,0,r,i),o.save(),R==="left"&&(o.translate(r,0),o.scale(-1,1)),E.time=f,T(o,E,f),o.restore()}function c(){!l&&h&&!w?.matches&&!document.hidden&&a===null&&(a=window.requestAnimationFrame($))}function $(t){a=null,!l&&(f+=u===null?0:Math.max(0,Math.min(50,t-u)),u=t,v(),c())}function y(){a!==null&&window.cancelAnimationFrame(a),a=null,u=null}function p(){y(),c()}return document.addEventListener("visibilitychange",p),w?.addEventListener?.("change",p),v(),c(),{canvas:e,character:n,play(){l||(h=!0,c())},pause(){h=!1,y()},replay(){l||(f=0,u=null,v(),h=!0,c())},destroy(){l||(l=!0,y(),document.removeEventListener("visibilitychange",p),w?.removeEventListener?.("change",p),e.remove())}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var w=Object.defineProperty;var b=(u,e,i)=>e in u?w(u,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):u[e]=i;var v=(u,e,i)=>b(u,typeof e!="symbol"?e+"":e,i);import{resolveCelebrationTraits as M}from"./trait-data.mjs";import{CanvasManager as P}from"./core/canvas.js";import{PhysicsEngine as C,updateMovingParticle as z,updateParticlePhysics as j}from"./core/physics.js";import{createParticle as F}from"./core/particles.js";const c=class c{static isDateInRange(e,i,s,o){const
|
|
1
|
+
var w=Object.defineProperty;var b=(u,e,i)=>e in u?w(u,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):u[e]=i;var v=(u,e,i)=>b(u,typeof e!="symbol"?e+"":e,i);export{mountCharacter,getCharacters,CHARACTER_SIZES}from"./characters.js";import{resolveCelebrationTraits as M}from"./trait-data.mjs";import{CanvasManager as P}from"./core/canvas.js";import{PhysicsEngine as C,updateMovingParticle as z,updateParticlePhysics as j}from"./core/physics.js";import{createParticle as F}from"./core/particles.js";const c=class c{static isDateInRange(e,i,s,o){const l=new Date,n=l.getMonth()+1,d=l.getDate();return s<e?n===e&&d>=i||n>e||n<e&&n<=s||n===s&&d<=o:(n>e||n===e&&d>=i)&&(n<s||n===s&&d<=o)}static getCurrentTheme(){for(const[e,i]of Object.entries(c.themes)){const[[s,o],[l,n]]=i.dates;if(c.isDateInRange(s,o,l,n))return e}return null}static isCelebrationSeason(){return c.getCurrentTheme()!==null}static getThemes(){return c.themes}constructor(e={}){this.options={theme:e.theme||"auto",intensity:e.intensity||"medium",enabled:e.enabled!==void 0?e.enabled:!0,zIndex:e.zIndex||999,overrides:e.overrides||{}},this.canvasManager=null,this.physicsEngine=null,this.themeModule=null,this.currentTheme=null,this.particles=[],this.specialParticles=[],this.animationFrame=null,this.lastTime=0,this.running=!1,this.initialized=!1}async init(){if(this.initialized){console.warn("[Celebrations] Already initialized");return}console.log("[Celebrations] Initializing...");let e=this.options.theme;if(e==="auto"&&(e=c.getCurrentTheme(),!e)){console.log("[Celebrations] No active celebration detected");return}if(!c.themes[e]){console.error(`[Celebrations] Unknown theme: ${e}`);return}this.currentTheme=e,console.log(`[Celebrations] Loading theme: ${e}`);try{const s=await import(c.themes[e].module);this.themeModule=s.default,console.log(`[Celebrations] Theme loaded: ${this.themeModule.displayName}`)}catch(i){console.error(`[Celebrations] Failed to load theme module: ${i.message}`);return}this.canvasManager=new P({canvasId:"celebrations-canvas",zIndex:this.options.zIndex}),this.canvasManager.create(),this.physicsEngine=new C({gustInterval:[5e3,15e3],gustStrength:[-2,2]}),window.addEventListener("resize",()=>{this.canvasManager.resize(),this.resetParticles()}),this.resetParticles(),this.initialized=!0,console.log("[Celebrations] Initialized successfully"),this.options.enabled&&this.start()}isParticleEnabled(e){return!this.disabledTypes?.has(e.type)}resetParticles(){const e=Object.assign({},this.themeModule.intensityConfig[this.options.intensity]),i=a=>String(a).toLowerCase().replace(/[^a-z0-9]/g,""),s=[...Object.keys(e),...this.themeModule.decorationKeys||[]],o=a=>s.find(r=>i(r)===i(a))||a,l=a=>["true","1","yes","on"].includes(String(a).toLowerCase()),n=this.themeModule.optInDecorations||{},d=Object.entries(this.options.overrides).some(([a,r])=>["snowonly","fallingonly"].includes(i(a))&&l(r));for(const a of Object.values(n))e[a.chance]=0;for(const[a,r]of Object.entries(this.options.overrides)){if((i(a)==="snowonly"||i(a)==="fallingonly")&&l(r)){for(const m of this.themeModule.decorationKeys||[])e[m]=0;continue}const p=Object.keys(n).find(m=>i(m)===i(a));if(p&&!d&&l(r)){const m=n[p];e[m.chance]=m.value}r==="off"&&(e[o(a)]=0)}const t=M(this.themeModule.name,this.options.overrides);Object.assign(e,t.config),this.disabledTypes=t.disabledTypes,this.disabledGlobals=t.disabledGlobals,this.config=e;const f=this.canvasManager.canvas.width,h=this.canvasManager.canvas.height;this.particles=[],this.specialParticles=[];const g=e.initialParticleRatio!==void 0?e.initialParticleRatio:1,y=Math.floor(e.count*g);if(this.themeModule.createFallingParticle)for(let a=0;a<y;a++){const r=this.themeModule.createFallingParticle(f,h,e);r.y=Math.random()*h,this.isParticleEnabled(r)&&this.particles.push(r)}else for(let a=0;a<y;a++){const r=F(e,f,h);r.y=Math.random()*h,this.isParticleEnabled(r)&&this.particles.push(r)}if(this.themeModule.createInitialDecorations){const a=this.themeModule.createInitialDecorations(f,h,e);this.specialParticles.push(...a.filter(r=>this.isParticleEnabled(r))),console.log(`[Celebrations] Created ${a.length} decorations`)}console.log(`[Celebrations] Created ${y}/${e.count} particles (${this.options.intensity}, ${Math.round(g*100)}% initial)`)}start(){if(!this.initialized){console.warn("[Celebrations] Cannot start - not initialized");return}if(this.running){console.warn("[Celebrations] Already running");return}console.log("[Celebrations] Starting animation"),this.running=!0,this.lastTime=performance.now(),this.animate()}pause(){this.running&&(console.log("[Celebrations] Pausing animation"),this.running=!1,this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=null))}resume(){this.running||this.start()}destroy(){console.log("[Celebrations] Destroying effect"),this.pause(),this.canvasManager&&this.canvasManager.canvas&&this.canvasManager.canvas.remove(),this.particles=[],this.specialParticles=[],this.canvasManager=null,this.physicsEngine=null,this.themeModule=null,this.initialized=!1}setIntensity(e){if(!["light","medium","heavy"].includes(e)){console.error(`[Celebrations] Invalid intensity: ${e}`);return}console.log(`[Celebrations] Changing intensity to: ${e}`),this.options.intensity=e,this.resetParticles()}async setTheme(e){console.log(`[Celebrations] Changing theme to: ${e}`);const i=this.running;this.pause(),await new Promise(s=>setTimeout(s,20)),this.initialized=!1,this.canvasManager&&this.canvasManager.clear(),this.particles=[],this.specialParticles=[],this.canvasManager&&this.canvasManager.clear(),this.options.theme=e,await this.init(),i&&this.start()}animate(){if(!this.running)return;const e=performance.now(),i=e-this.lastTime;this.lastTime=e,this.canvasManager.clear();const s=this.canvasManager.ctx,o=this.canvasManager.canvas.width,l=this.canvasManager.canvas.height,n=this.config;this.physicsEngine.updateWind(e);const d=this.physicsEngine.getWindForce();if(this.particles.length<n.count&&Math.random()<.05&&this.themeModule.createFallingParticle){const t=this.themeModule.createFallingParticle(o,l,n);(t.y===void 0||t.y<0)&&(t.y=-20),this.isParticleEnabled(t)&&this.particles.push(t)}if(this.particles.forEach((t,f)=>{if(j(t,i,d),t.y>l+50&&(t.y=-20,t.x=Math.random()*o),t.x<-50&&(t.x=o+50),t.x>o+50&&(t.x=-50),this.themeModule.drawSnowflake&&t.type==="snowflake")this.themeModule.drawSnowflake(s,t);else if(this.themeModule["draw"+this.capitalizeFirst(t.type)]){const h="draw"+this.capitalizeFirst(t.type);this.themeModule[h](s,t,e)}else s.save(),s.globalAlpha=t.opacity,s.fillStyle=this.themeModule.colors?.primary||"#ffffff",s.beginPath(),s.arc(t.x,t.y,t.size,0,Math.PI*2),s.fill(),s.restore()}),this.themeModule.updateSpecialParticles&&this.themeModule.updateSpecialParticles(this.specialParticles,i,o,l),this.specialParticles=this.specialParticles.filter(t=>{if(!t.active||!this.isParticleEnabled(t))return!1;if(!t.static){z(t,i,e);const g=t.type==="train"?600:200;if(t.x<-g||t.x>o+g)return t.active=!1,!1}if(!isFinite(t.x)||!isFinite(t.y)||t.size&&!isFinite(t.size))return console.warn("[Celebrations] Invalid particle values, removing:",t.type),t.active=!1,!1;const h="draw"+this.capitalizeFirst(t.type);return this.themeModule[h]&&this.themeModule[h](s,t,e),!0}),this.themeModule.spawnSpecialParticle){const t=this.themeModule.spawnSpecialParticle(this.specialParticles,o,l,n);t&&this.isParticleEnabled(t)&&this.specialParticles.push(t)}this.themeModule.drawGlobalEffects&&!this.disabledGlobals?.has("lightning")&&this.themeModule.drawGlobalEffects(s,e,o,l),this.animationFrame=requestAnimationFrame(()=>this.animate())}capitalizeFirst(e){if(!e)return"";const i=e.replace(/-([a-z])/g,(s,o)=>o.toUpperCase());return i.charAt(0).toUpperCase()+i.slice(1)}};v(c,"themes",{christmas:{module:"./themes/christmas.js?v=20261003-characters",dates:[[12,1],[1,1]],displayName:"Christmas",emoji:"\u{1F384}"},halloween:{module:"./themes/halloween.js?v=20261003-characters",dates:[[10,26],[10,31]],displayName:"Halloween",emoji:"\u{1F383}"},valentines:{module:"./themes/valentines.js?v=20261003-characters",dates:[[2,9],[2,14]],displayName:"Valentine's Day",emoji:"\u{1F495}"},"guy-fawkes":{module:"./themes/guy-fawkes.js?v=20261003-characters",dates:[[11,1],[11,5]],displayName:"Guy Fawkes Night",emoji:"\u{1F386}"},"st-patricks":{module:"./themes/st-patricks.js?v=20261003-characters",dates:[[3,12],[3,17]],displayName:"St Patrick's Day",emoji:"\u2618\uFE0F"},"st-andrews":{module:"./themes/st-andrews.js?v=20261003-characters",dates:[[11,25],[11,30]],displayName:"St Andrew's Day",emoji:"\u{1F3F4}\u{E0067}\u{E0062}\u{E0073}\u{E0063}\u{E0074}\u{E007F}"},"st-davids":{module:"./themes/st-davids.js?v=20261003-characters",dates:[[2,24],[3,1]],displayName:"St David's Day",emoji:"\u{1F3F4}\u{E0067}\u{E0062}\u{E0077}\u{E006C}\u{E0073}\u{E007F}"},"st-georges":{module:"./themes/st-georges.js?v=20261003-characters",dates:[[4,18],[4,23]],displayName:"St George's Day",emoji:"\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}"}});export let CelebrationsEffect=c;export default CelebrationsEffect;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createParticle as T}from"./../core/particles.js";import{drawSleigh as c,drawTrain as
|
|
1
|
+
import{createParticle as T}from"./../core/particles.js";import{drawSleigh as c,drawTrain as C}from"./christmas-vehicles.js";import{drawTree as z,drawWreath as P,drawRobin as x,drawElf as b,drawSnowman as E,TREE_GIFTS as Y}from"./christmas-festive.js";import{drawMooningElf as R}from"./mooning-drawing.js";import{spawnMooningCharacter as I,updateMooningCharacter as X}from"./mooning-antics.js";import{spawnElfAntic as F,updateElfAntic as A}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","mooning-elf"],decorationKeys:["trees","wreaths","northStars","snowmen","sleigh","elf","train","firework","robin","peeingElfChance","thievingElfChance","mooningElfChance"],optInDecorations:{mooningElf:{chance:"mooningElfChance",value:7e-4},peeingElf:{chance:"peeingElfChance",value:7e-4},thievingElf:{chance:"thievingElfChance",value:.001}},colors:{primary:"#ffffff",secondary:"#228B22",accent:"#c00",gold:"#FFD700"},createSnowflakeParticle(t,n,o){const i=T(o,t,n);return i.type="snowflake",i},createFallingParticle(t,n,o){return this.createSnowflakeParticle(t,n,o)},createTree(t,n,o={}){return{type:"tree",gifts:Y.map(i=>({...i,stolen:!1,reserved:!1})),x:o.x!==void 0?o.x:Math.random()*t,y:o.y!==void 0?o.y:Math.random()*n,vx:0,vy:0,size:20+Math.random()*15,opacity:.6+Math.random()*.3,rotation:0,rotationSpeed:0,active:!0,static:!0}},createWreath(t,n,o={}){const i=[];for(let a=0;a<20;a++){const s=a/20*Math.PI*2;i.push({angle:s,radius:.9+Math.random()*.2,thickness:.2+Math.random()*.15,color:a%2===0?"#1a6b1a":"#228B22"})}return{type:"wreath",x:o.x!==void 0?o.x:Math.random()*t,y:o.y!==void 0?o.y:Math.random()*n,vx:0,vy:0,size:15+Math.random()*10,opacity:.7+Math.random()*.2,rotation:0,rotationSpeed:0,active:!0,static:!0,shape:i}},createNorthStar(t,n,o={}){return{type:"north-star",x:o.x!==void 0?o.x:t/2,y:o.y!==void 0?o.y:80,vx:0,vy:0,size:25,opacity:1,twinklePhase:Math.random()*Math.PI*2,twinkleSpeed:.003,active:!0,static:!0}},createSnowman(t,n,o={}){return{type:"snowman",x:o.x!==void 0?o.x:Math.random()*t,y:o.y!==void 0?o.y:n-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(t,n,o){const i=[],e=o.trees??6;for(let r=0;r<e;r++)i.push(this.createTree(t,n,{x:t/(e+1)*(r+1),y:n-60-Math.random()*20}));const a=o.wreaths??3;for(let r=0;r<a;r++)i.push(this.createWreath(t,n,{x:t/(a+1)*(r+1),y:50+Math.random()*100}));o.northStars!==0&&o.northStars&&i.push(this.createNorthStar(t,n,{x:t/2,y:60}));const s=o.snowmen??3;for(let r=0;r<s;r++)i.push(this.createSnowman(t,n,{x:t/(s+1)*(r+1),y:n-50-Math.random()*10}));return i},spawnSpecialParticle(t,n,o,i={}){const e=I(t,n,o,i,"mooning-elf","mooningElfChance");if(e)return e;const a=F(t,n,o,i);if(a)return a;const s=Math.random();if(s<5e-4){if(i.sleigh===0||t.some(d=>d.type==="sleigh"))return null;const r=Math.random()<.5,f=r?-100:n+100,h=100+Math.random()*(o*.3);return{type:"sleigh",x:f,startX:f,y:h,baseY:h,targetX:r?n+100:-100,canvasWidth:n,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(s<.0013&&i.elf!==0){const r=Math.random()<.5;return{type:"elf",x:r?-50:n+50,y:o-30,baseY:o-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(s<.005&&i.train!==0){if(t.some(y=>y.type==="train"))return null;const r=Math.random()<.5,f=r?-500:n+500,h=21+Math.random()*9,d=h*.72,m=o-d-10;return{type:"train",x:f,y:m,baseY:m,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(s<.008&&i.firework!==0)return{type:"firework",x:Math.random()*n,y:o,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(s<.012&&i.robin!==0){if(t.some(l=>l.type==="robin"))return null;const r=Math.random()<.5,f=Math.random()*(o*.2),h=r?-50:n+50,d=10+Math.random()*5,m=Math.max(d*3,Math.min(o*.6-d*2,o*.2+Math.random()*(o*.4))),y=Math.random()*(n*.6)+n*.2;return{type:"robin",state:"flying_in",x:h,y:f,startX:h,startY:f,targetX:y,targetY:m,vx:r?.5+Math.random()*.5:-(.5+Math.random()*.5),vy:0,size:d,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(t,n,o=1024,i=768){t.forEach(e=>{if(e.type==="mooning-elf"){X(e,n,o);return}switch(e.time!==void 0&&(e.time+=n),e.type){case"peeing-elf":case"thieving-elf":A(e,n,o);break;case"sleigh":(e.vx>0&&e.x>e.targetX||e.vx<0&&e.x<e.targetX)&&(e.active=!1);break;case"robin":switch(e.state){case"flying_in":const a=e.targetX-e.x,s=e.targetY-e.y,r=Math.sqrt(a*a+s*s);if(r<20)e.state="sitting",e.vx=0,e.vy=0,e.sitStartTime=e.time;else{const M=a/r,g=s/r,v=3;e.vx=M*v;const u=30,w=.008,k=Math.sin(e.time*w+e.waveOffset)*u,S=Math.cos(e.time*w+e.waveOffset)*u*w;e.vy=g*v*.3+S}break;case"sitting":if(e.vx=0,e.vy=0,e.time-e.sitStartTime>e.sitTime){e.state="flying_away";const M=e.x<o/2?-4:4;e.vx=M,e.flyAwayStartTime=e.time}break;case"flying_away":const f=e.vx>0?1:-1;e.vx=f*4;const h=25,d=.01,m=e.time-e.flyAwayStartTime,y=Math.sin(m*d+e.waveOffset)*h,l=Math.cos(m*d+e.waveOffset)*h*d;e.vy=-1.5+l,(e.x<-50||e.x>o+50||e.y<-50)&&(e.active=!1);break}break;case"train":if(e.smoke||(e.smoke=[]),(e.vx>0&&e.x>o+500||e.vx<0&&e.x<-500)&&(e.active=!1),e.lastSmokeTime||(e.lastSmokeTime=0),e.time-e.lastSmokeTime>150){e.lastSmokeTime=e.time;const s=e.size*1.8/20,r=e.vx>0?1:-1,f=s*8,h=s*7,d=s*10,l=-f-s-h-d*2-s*8,M=s*70,g=s*25,u=(M-g)*.7,w=e.x+u*r,k=e.y+l,S={x:w,y:k,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};e.smoke.push(S)}e.smoke=e.smoke.filter(a=>(a.x+=a.vx,a.y+=a.vy,a.vy*=.99,a.size*=1.02,a.opacity-=a.fadeRate,a.opacity>0));break;case"firework":if(!e.exploded){const a=e.y<=e.targetY,s=e.time>=e.explosionTime;(a||s)&&(e.exploded=!0,this.explodeFirework(e,t),e.active=!1)}break}})},drawSnowflake(t,n){t.save(),t.translate(n.x,n.y),t.rotate(n.rotation),t.globalAlpha=n.opacity,t.fillStyle="#ffffff",t.strokeStyle="#ffffff",t.lineWidth=Math.max(n.size*.15,.5);const o=6,i=n.size;for(let e=0;e<o;e++){const a=Math.PI*2*e/o;t.beginPath(),t.moveTo(0,0),t.lineTo(Math.cos(a)*i,Math.sin(a)*i),t.stroke();const s=i*.4,r=Math.PI/6,f=Math.cos(a)*(i*.6),h=Math.sin(a)*(i*.6);t.beginPath(),t.moveTo(f,h),t.lineTo(f+Math.cos(a-r)*s,h+Math.sin(a-r)*s),t.stroke(),t.beginPath(),t.moveTo(f,h),t.lineTo(f+Math.cos(a+r)*s,h+Math.sin(a+r)*s),t.stroke()}t.restore()},drawTree:z,drawWreath:P,drawNorthStar(t,n,o){const i=n.x,e=n.y,a=n.size,s=.8+Math.sin(o*n.twinkleSpeed+n.twinklePhase)*.2;t.save(),t.translate(i,e);const r=a*4*s,f=t.createRadialGradient(0,0,0,0,0,r);f.addColorStop(0,"rgba(220, 230, 240, 0.4)"),f.addColorStop(.4,"rgba(200, 210, 220, 0.2)"),f.addColorStop(1,"rgba(180, 190, 200, 0)"),t.fillStyle=f,t.beginPath(),t.arc(0,0,r,0,Math.PI*2),t.fill();const h=a*1,d=a*1.5,m=a*.12,y=t.createRadialGradient(0,0,0,0,0,a);y.addColorStop(0,"#ffffff"),y.addColorStop(.3,"#f0f0f0"),y.addColorStop(.6,"#c0c0c0"),y.addColorStop(1,"#a0a0a0"),t.shadowColor="rgba(220, 230, 240, 0.9)",t.shadowBlur=a*.8*s,t.fillStyle=y,t.beginPath(),t.moveTo(0,-d),t.lineTo(m,-m),t.lineTo(h,0),t.lineTo(m,m),t.lineTo(0,d),t.lineTo(-m,m),t.lineTo(-h,0),t.lineTo(-m,-m),t.closePath(),t.fill(),t.shadowBlur=a*1.2*s,t.shadowColor="rgba(255, 255, 255, 1)";const l=t.createRadialGradient(0,0,0,0,0,a*.3);l.addColorStop(0,"#ffffff"),l.addColorStop(.6,"#f5f5f5"),l.addColorStop(1,"rgba(200, 200, 200, 0.8)"),t.fillStyle=l,t.beginPath(),t.arc(0,0,a*.3,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.restore()},drawSleigh:c,drawElf:b,drawMooningElf:R,drawPeeingElf:b,drawThievingElf:b,drawTrain:C,drawFirework(t,n){t.fillStyle=n.color||"#ffffff",t.beginPath(),t.arc(n.x,n.y,n.size,0,Math.PI*2),t.fill()},explodeFirework(t,n){const o=50+Math.random()*50,i=["#ff0000","#ffff00","#00ff00","#0000ff","#ffffff"];for(let e=0;e<o;e++){const a=Math.random()*Math.PI*2,s=Math.random()*5+2;n.push({type:"spark",x:t.x,y:t.y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,size:2+Math.random()*2,opacity:1,active:!0,static:!1,color:i[Math.floor(Math.random()*i.length)]})}},drawRobin:x,drawSnowman:E};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function spawnMooningCharacter(t,e,a,s,n,o){if(!(s[o]>0)||t.some(g=>g.active&&g.type===n)||Math.random()>=s[o])return null;const x=Math.random()<.5,m=16+Math.random()*4;return{type:n,size:m,x:x?-m*2:e+m*2,y:a-m*1.4-8,vx:x?1:-1,vy:0,targetX:e*(.3+Math.random()*.4),opacity:1,time:0,stateTime:0,state:"approaching",active:!0,static:!0}}export function updateMooningCharacter(t,e,a){const s=Math.max(0,Math.min(Number(e)||0,50));if(t.time+=s,t.stateTime+=s,t.state==="approaching"){const n=t.targetX-t.x,o=s*.08;Math.abs(n)<=o?(t.x=t.targetX,t.state="mooning",t.stateTime=0):(t.vx=Math.sign(n),t.x+=t.vx*o)}else t.state==="mooning"&&t.stateTime>=4200?(t.state="escaping",t.stateTime=0,t.vx=t.x<a/2?-1:1):t.state==="escaping"&&(t.x+=t.vx*s*.12,(t.x<-t.size*2||t.x>a+t.size*2)&&(t.active=!1))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{path as r,ellipse as n,gradient as T,rect as i}from"./christmas-drawing.js";import{drawElf as b}from"./christmas-festive.js";import{drawLeprechaun as C}from"./character-drawing.js";function f(o,e,u,l){if(![e.x,e.y,e.size].every(Number.isFinite)||e.size<=0)return;if(e.state&&e.state!=="mooning"){(l?C:b)(o,e,u);return}o.save(),o.translate(e.x,e.y),o.scale(e.size*(e.vx<0?-1:1),e.size),o.globalAlpha*=e.opacity??1,o.lineCap="round",o.lineJoin="round";const v=Math.sin(u*.005)*.055;o.rotate(v);const d=l?"#376852":"#315c43",s=T(o,.18,.8,["#f2d0ad","#dda382"]);for(const a of[-1,1]){if(i(o,a*.22-.065,.7,.13,.53,.04,"#e5d9ba"),!l)for(const m of[.88,1.02,1.14])i(o,a*.22-.068,m,.136,.045,0,"#b8444a");r(o,[["moveTo",a*.22-.1,1.18],["lineTo",a*.22+.09,1.18],["quadraticCurveTo",a*.22+.29,1.2,a*.22+.27,1.33],["lineTo",a*.22-.12,1.32],["closePath"]],"#493c37"),i(o,a*.22-.04,1.2,.1,.065,.012,"#d6b278")}i(o,-.39,.75,.78,.23,.075,d,"#a0b095",.02);for(const a of[-.24,-.08,.12,.28])r(o,[["moveTo",a,.78],["lineTo",a-.03,.94]],null,"#203f34",.018);r(o,[["moveTo",-.26,-.47],["quadraticCurveTo",-.47,-.18,-.36,.29],["quadraticCurveTo",0,.42,.37,.29],["quadraticCurveTo",.46,-.18,.25,-.47],["closePath"]],T(o,-.5,.4,["#73966c",d])),n(o,-.17,.49,.26,.3,s),n(o,.17,.49,.26,.3,s),r(o,[["moveTo",0,.35],["quadraticCurveTo",-.025,.57,0,.74]],null,"#bd846d",.018);for(const a of[-1,1])r(o,[["moveTo",a*.25,-.3],["quadraticCurveTo",a*.59,.02,a*.43,.58]],null,d,.15),n(o,a*.41,.65,.075,.09,"#ecc2a0");n(o,.03,-.68,.28,.31,"#e7b994"),l?(r(o,[["moveTo",.2,-.63],["quadraticCurveTo",.5,-.5,.28,-.3],["lineTo",.13,-.48]],"#a36643"),i(o,-.27,-1.35,.6,.62,.04,d,"#8ba17e",.018),i(o,-.27,-.98,.6,.12,0,"#414c3e"),i(o,.08,-1,.16,.16,.02,"#dbb779"),i(o,.115,-.965,.09,.09,0,d),n(o,.02,-.75,.46,.065,d)):(r(o,[["moveTo",-.23,-.68],["lineTo",-.42,-.84],["lineTo",-.25,-.87],["closePath"]],"#e7b994"),r(o,[["moveTo",-.27,-.93],["quadraticCurveTo",-.13,-1.46,.29,-1.28],["quadraticCurveTo",.5,-1.22,.54,-1.02],["quadraticCurveTo",.29,-1.19,.3,-.92],["closePath"]],T(o,-1.4,-.9,["#d56862","#a33747"])),r(o,[["moveTo",-.27,-.92],["quadraticCurveTo",0,-1,.3,-.92]],null,"#d9b878",.065),n(o,.54,-1.02,.065,.075,"#f1ce7e")),n(o,.29,-.62,.09,.06,"#dfa582"),n(o,.19,-.71,.025,.033,"#423736"),r(o,[["moveTo",.15,-.54],["quadraticCurveTo",.26,-.47,.32,-.55]],null,"#8b5949",.018),o.restore()}export function drawMooningElf(o,e,u=e.time||0){f(o,e,u,!1)}export function drawMooningLeprechaun(o,e,u=e.time||0){f(o,e,u,!0)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{drawLeprechaun as m,drawStaticLeprechaun as w}from"./character-drawing.js";export default{name:"st-patricks",displayName:"St Patrick's Day",emoji:"\u2618\uFE0F",intensityConfig:{light:{count:40,speedRange:[.4,1.2],sizeRange:[2,4],pots:1,leprechaunChance:3e-4,twinklingStars:10},medium:{count:80,speedRange:[.5,1.5],sizeRange:[2,5],pots:2,leprechaunChance:5e-4,twinklingStars:18},heavy:{count:120,speedRange:[.6,1.8],sizeRange:[3,6],pots:3,leprechaunChance:8e-4,twinklingStars:25}},particles:["clover-petal","shamrock","gold-coin","sparkle"],decorations:["pot-of-gold","rainbow","leprechaun","harp","static-leprechaun","moon","twinkling-star"],colors:{primary:"#228B22",secondary:"#90EE90",white:"#FFFFFF",accent:"#FFD700",rainbow:["#ff0000","#ffa500","#ffff00","#00ff00","#0000ff","#4b0082","#8b00ff"]},createCloverPetal(e,o,n){const t=["#228B22","#32CD32","#90EE90","#3CB371","#2E8B57"];return{type:"clover-petal",x:-30,y:Math.random()*o,vx:n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]),size:(n.sizeRange[0]+Math.random()*(n.sizeRange[1]-n.sizeRange[0]))*1.5,speed:(Math.random()-.5)*.2,opacity:.75+Math.random()*.25,windOffset:Math.random()*Math.PI*2,windSpeed:.015+Math.random()*.025,rotation:Math.random()*Math.PI*2,rotationSpeed:(Math.random()-.5)*.03,color:t[Math.floor(Math.random()*t.length)],flutter:Math.random()*Math.PI*2,flutterSpeed:.02+Math.random()*.02,active:!0}},createShamrock(e,o,n){const t=Math.random();let r,a;return t<.5?(r="#228B22",a="#006400"):t<.75?(r="#FFFFFF",a="#d0d0d0"):(r="#FFD700",a="#DAA520"),{type:"shamrock",x:-30,y:Math.random()*o,vx:n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]),size:n.sizeRange[0]+Math.random()*(n.sizeRange[1]-n.sizeRange[0]),speed:(Math.random()-.5)*.15,opacity:.7+Math.random()*.3,windOffset:Math.random()*Math.PI*2,windSpeed:.015+Math.random()*.025,rotation:Math.random()*Math.PI*2,rotationSpeed:(Math.random()-.5)*.02,color:r,strokeColor:a,active:!0}},createGoldCoin(e,o,n){return{type:"gold-coin",x:-30,y:Math.random()*o,vx:(n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]))*.8,size:2+Math.random()*3,speed:(Math.random()-.5)*.1,opacity:.9+Math.random()*.1,rotation:0,rotationSpeed:.05+Math.random()*.1,spinPhase:Math.random()*Math.PI*2,glintPhase:Math.random()*Math.PI*2,windOffset:Math.random()*Math.PI*2,windSpeed:.01+Math.random()*.02,active:!0}},createPotOfGold(e,o,n={}){return{type:"pot-of-gold",x:n.x!==void 0?n.x:Math.random()*e*.6+e*.2,y:n.y!==void 0?n.y:o-40,size:20+Math.random()*10,opacity:1,glintPhase:Math.random()*Math.PI*2,coins:[],active:!0,static:!0}},createRainbow(e,o,n,t){const a=n,s=(20+n)/2,d=o*.2,i=t-d/2;return{type:"rainbow",x:s,y:i,startX:20,endX:a,endY:t,width:e,height:o,opacity:.7,vx:0,vy:0,active:!0,static:!0}},createSparkle(e,o,n){const t=["#FFD700","#FFFFFF","#228B22"];return{type:"sparkle",x:-20,y:Math.random()*o,vx:(n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]))*.8,size:n.sizeRange[0]+Math.random()*(n.sizeRange[1]-n.sizeRange[0])*.6,vy:(Math.random()-.5)*.2,opacity:.6+Math.random()*.4,rotation:Math.random()*Math.PI*2,rotationSpeed:(Math.random()-.5)*.04,color:t[Math.floor(Math.random()*t.length)],twinklePhase:Math.random()*Math.PI*2,windOffset:Math.random()*Math.PI*2,windSpeed:.015+Math.random()*.02,active:!0,static:!1}},createFallingParticle(e,o,n){const t=Math.random();return t<.6?this.createCloverPetal(e,o,n):t<.8?this.createShamrock(e,o,n):t<.95?this.createGoldCoin(e,o,n):this.createSparkle(e,o,n)},createInitialDecorations(e,o,n){const t=[];t.push({type:"moon",x:e-120,y:100,size:60+Math.random()*20,opacity:.85,glowPhase:Math.random()*Math.PI*2,active:!0,static:!0});const r=20,a=220,s=o-40;t.push(this.createPotOfGold(e,o,{x:a,y:s})),t.push(this.createRainbow(e,o,a,s));const d=1+Math.floor(Math.random()*2);for(let M=0;M<d;M++){const y=60+Math.random()*(e-120);t.push({type:"static-leprechaun",x:y,y:o-35,size:12+Math.random()*4,opacity:1,time:Math.random()*1e3,wavePhase:Math.random()*Math.PI*2,active:!0,static:!0})}const i=n.twinklingStars??18;for(let M=0;M<i;M++)t.push(this.createTwinklingStar(e,o));return t},createTwinklingStar(e,o){return{type:"twinkling-star",x:Math.random()*e,y:Math.random()*(o*.5),size:1+Math.random()*2,opacity:.6+Math.random()*.3,twinklePhase:Math.random()*Math.PI*2,twinkleSpeed:.003+Math.random()*.003,active:!0,static:!0}},spawnSpecialParticle(e,o,n,t){const r=Math.random();if(r<t.leprechaunChance){if(e.some(i=>i.type==="leprechaun"))return null;const a=Math.random()<.5,s=(o+100)/180;return{type:"leprechaun",x:a?-50:o+50,y:n-35,baseY:n-35,vx:a?s:-s,vy:0,size:12+Math.random()*6,opacity:1,time:0,legPhase:Math.random()*Math.PI*2,active:!0,static:!1}}if(r<t.leprechaunChance+3e-4){if(e.some(s=>s.type==="banshee"))return null;const a=Math.random()<.5;return{type:"banshee",x:a?-100:o+100,y:Math.random()*(n*.4)+50,baseY:Math.random()*(n*.4)+50,vx:a?2+Math.random()*1:-(2+Math.random()*1),size:18+Math.random()*10,opacity:.6+Math.random()*.2,waveAmplitude:25+Math.random()*20,waveFrequency:.0015+Math.random()*.002,waveOffset:Math.random()*Math.PI*2,time:0,wailPhase:Math.random()*Math.PI*2,active:!0,static:!1}}if(r<.001&&e.filter(s=>s.type==="pot-of-gold").length<t.pots){const s=this.createPotOfGold(o,n),d=this.createRainbow(o,n,s.x,s.y);return e.push(d),s}return null},updateSpecialParticles(e,o,n,t){return e.forEach(r=>{r.active&&((r.type==="leprechaun"||r.type==="banshee")&&(r.time+=o,r.type==="leprechaun"?(r.vx>0&&r.x>n+r.size*2||r.vx<0&&r.x<-r.size*2)&&(r.active=!1):r.type==="banshee"&&(r.vx>0&&r.x>n+r.size*4||r.vx<0&&r.x<-r.size*4)&&(r.active=!1)),r.type==="static-leprechaun"&&(r.time+=o))}),e.filter(r=>r.active)},drawCloverPetal(e,o,n){const t=o.x,r=o.y,a=o.size,s=Math.sin(n*o.flutterSpeed+o.flutter)*.3;e.save(),e.globalAlpha=o.opacity,e.translate(t,r),e.rotate(o.rotation+s),e.fillStyle=o.color,e.strokeStyle="#006400",e.lineWidth=a*.08,e.beginPath(),e.moveTo(0,-a*.3),e.bezierCurveTo(-a*.6,-a*.7,-a*.8,-a*.3,-a*.5,a*.2),e.lineTo(0,a*.8),e.lineTo(a*.5,a*.2),e.bezierCurveTo(a*.8,-a*.3,a*.6,-a*.7,0,-a*.3),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="rgba(0, 100, 0, 0.4)",e.lineWidth=a*.06,e.beginPath(),e.moveTo(0,-a*.2),e.lineTo(0,a*.7),e.stroke(),e.restore()},drawSparkle(e,o,n){const t=o.x,r=o.y,a=o.size,s=.6+Math.sin(n*.004+o.twinklePhase)*.4;e.save(),e.globalAlpha=o.opacity*s,e.translate(t,r),e.rotate(o.rotation),e.fillStyle=o.color,e.shadowColor=o.color,e.shadowBlur=a*2,e.beginPath(),e.moveTo(0,-a),e.lineTo(a*.3,-a*.3),e.lineTo(a,0),e.lineTo(a*.3,a*.3),e.lineTo(0,a),e.lineTo(-a*.3,a*.3),e.lineTo(-a,0),e.lineTo(-a*.3,-a*.3),e.closePath(),e.fill(),e.restore()},drawShamrock(e,o){const n=o.x,t=o.y,r=o.size;e.save(),e.globalAlpha=o.opacity,e.translate(n,t),e.rotate(o.rotation),e.fillStyle=o.color||"#228B22",e.strokeStyle=o.strokeColor||"#006400",e.lineWidth=r*.1;for(let a=0;a<3;a++){const s=a/3*Math.PI*2-Math.PI/2;e.save(),e.rotate(s),e.translate(0,-r*.7),e.beginPath(),e.moveTo(0,r*.3),e.bezierCurveTo(-r*.5,-r*.2,-r*.3,-r*.6,0,-r*.35),e.bezierCurveTo(r*.3,-r*.6,r*.5,-r*.2,0,r*.3),e.closePath(),e.fill(),e.stroke(),e.restore()}e.strokeStyle="#8B4513",e.lineWidth=r*.15,e.beginPath(),e.moveTo(0,0),e.lineTo(0,r*1.2),e.stroke(),e.restore()},drawGoldCoin(e,o,n){const t=o.x,r=o.y,a=o.size,s=Math.abs(Math.cos(o.rotation))*a;if(e.save(),e.globalAlpha=o.opacity,e.translate(t,r),e.fillStyle="#FFD700",e.strokeStyle="#DAA520",e.lineWidth=a*.15,e.beginPath(),e.ellipse(0,0,s,a,0,0,Math.PI*2),e.fill(),e.stroke(),s>a*.3){const d=(Math.sin(n*.005+o.glintPhase)+1)*.5;e.fillStyle=`rgba(255, 255, 255, ${d*.6})`,e.beginPath(),e.ellipse(-s*.3,-a*.3,s*.3,a*.3,0,0,Math.PI*2),e.fill()}e.restore()},drawPotOfGold(e,o,n){const t=o.x,r=o.y,a=o.size;e.save(),e.translate(t,r),e.fillStyle="#1a1a1a",e.strokeStyle="#0a0a0a",e.lineWidth=2,e.beginPath(),e.arc(0,0,a*.7,0,Math.PI),e.lineTo(-a*.7,a*.4),e.quadraticCurveTo(0,a*.6,a*.7,a*.4),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="#2a2a2a",e.lineWidth=3,e.beginPath(),e.arc(0,-a*.2,a*.5,Math.PI,0),e.stroke();const s=[{x:-a*.4,y:-a*.2,s:.8},{x:a*.3,y:-a*.3,s:.9},{x:-a*.1,y:-a*.5,s:1},{x:a*.5,y:-a*.1,s:.7},{x:-a*.5,y:0,s:.75},{x:a*.1,y:-a*.6,s:.85},{x:0,y:-a*.3,s:.95}];e.fillStyle="#FFD700",e.strokeStyle="#DAA520",e.lineWidth=1,s.forEach((i,M)=>{const y=n*.002+M*.5,P=(Math.sin(y)+1)*.5;e.save(),e.translate(i.x,i.y),e.beginPath(),e.arc(0,0,a*.2*i.s,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle=`rgba(255, 255, 255, ${P*.6})`,e.beginPath(),e.arc(-a*.08*i.s,-a*.08*i.s,a*.08*i.s,0,Math.PI*2),e.fill(),e.restore()}),e.globalAlpha=.3;const d=e.createRadialGradient(0,-a*.2,0,0,-a*.2,a*1.5);d.addColorStop(0,"#FFD700"),d.addColorStop(1,"rgba(255, 215, 0, 0)"),e.fillStyle=d,e.fillRect(-a*1.5,-a*1.5,a*3,a*1.5),e.restore()},drawSmallGoldPot(e,o,n,t){e.save(),e.translate(o,n),e.fillStyle="#1a1a1a",e.strokeStyle="#0a0a0a",e.lineWidth=1,e.beginPath(),e.arc(0,-t*.4,t*.7,0,Math.PI,!0),e.lineTo(t*.7,t*.4),e.quadraticCurveTo(0,t*.6,-t*.7,t*.4),e.closePath(),e.fill(),e.stroke(),e.fillStyle="#FFD700",e.beginPath(),e.arc(0,-t*.7,t*.5,0,Math.PI*2),e.fill(),e.restore()},drawRainbow(e,o){const n=o.startX,t=o.endX,r=o.endY,a=o.height;e.save(),e.globalAlpha=.5;const s=Math.abs(t-n),d=(n+t)/2,i=a*.2,M=(s*s+4*i*i)/(8*i),y=r+i-M,P=this.colors.rainbow,g=5;for(let h=P.length-1;h>=0;h--)e.strokeStyle=P[h],e.lineWidth=g,e.beginPath(),e.arc(d,y,M+(P.length-1-h)*g,Math.PI,0),e.stroke();e.restore(),this.drawSmallGoldPot(e,t,r,20)},drawLeprechaun:m,drawBanshee(e,o,n){const t=o.x,r=o.y+Math.sin(n*o.waveFrequency+o.waveOffset)*o.waveAmplitude,a=o.size,s=o.vx>0?1:-1;e.save(),e.globalAlpha=o.opacity,e.translate(t,r),s===-1&&e.scale(-1,1);const d=e.createRadialGradient(0,-a*.5,0,0,-a*.5,a*3);d.addColorStop(0,"rgba(180, 255, 180, 0.4)"),d.addColorStop(.3,"rgba(150, 255, 150, 0.2)"),d.addColorStop(.6,"rgba(120, 255, 120, 0.1)"),d.addColorStop(1,"rgba(100, 255, 100, 0)"),e.fillStyle=d,e.fillRect(-a*3,-a*3,a*6,a*6);const i=Math.sin(n*.008+o.wailPhase)*a*.25;e.fillStyle="rgba(200, 240, 200, 0.3)",e.beginPath(),e.moveTo(-a*.8,-a*1.2),e.bezierCurveTo(-a*1.2+i*2,-a*.4,-a*1+i*1.5,a*.6,-a*.6+i,a*1.8),e.bezierCurveTo(-a*.3,a*1.5,a*.3,a*1.5,a*.6-i,a*1.8),e.bezierCurveTo(a*1-i*1.5,a*.6,a*1.2-i*2,-a*.4,a*.8,-a*1.2),e.closePath(),e.fill(),e.fillStyle="rgba(230, 255, 230, 0.85)",e.strokeStyle="rgba(180, 255, 180, 0.6)",e.lineWidth=a*.04,e.beginPath(),e.moveTo(-a*.5,-a*1);for(let l=0;l<=12;l++){const h=l/12,f=Math.sin(n*.012+h*Math.PI*3+o.wailPhase)*a*.3,b=(h-.5)*a*1.8+f,u=-a*1+h*a*2.2;l===0?e.moveTo(b,u):e.lineTo(b,u)}e.lineTo(-a*.5,-a*1),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="rgba(180, 255, 180, 0.4)",e.lineWidth=a*.02;for(let l=0;l<8;l++){const h=-a*.7+l*(a*1.4/7),f=Math.sin(n*.015+l*.8)*a*.2;e.beginPath(),e.moveTo(h,a*.4),e.lineTo(h+f,a*1.2+l%2*a*.4),e.stroke()}const M=Math.sin(n*.006)*.15;e.save(),e.rotate(M),e.fillStyle="rgba(240, 255, 240, 0.9)",e.strokeStyle="rgba(180, 230, 180, 0.5)",e.lineWidth=a*.03,e.beginPath(),e.ellipse(0,-a*.8,a*.35,a*.55,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="rgba(50, 100, 50, 0.6)",e.beginPath(),e.ellipse(-a*.15,-a*.85,a*.12,a*.08,0,0,Math.PI*2),e.ellipse(a*.15,-a*.85,a*.12,a*.08,0,0,Math.PI*2),e.fill();const y=.6+Math.sin(n*.008+o.wailPhase)*.4;e.fillStyle=`rgba(100, 255, 150, ${y})`,e.shadowColor="#64ff96",e.shadowBlur=a*.6,e.beginPath(),e.arc(-a*.15,-a*.85,a*.08,0,Math.PI*2),e.arc(a*.15,-a*.85,a*.08,0,Math.PI*2),e.fill(),e.shadowBlur=0;const P=.2+Math.sin(n*.01+o.wailPhase)*.1;e.fillStyle="rgba(80, 150, 80, 0.7)",e.strokeStyle="rgba(50, 100, 50, 0.8)",e.lineWidth=a*.02,e.beginPath(),e.ellipse(0,-a*.6,a*.15,a*(.2+P),0,0,Math.PI*2),e.fill(),e.stroke(),e.restore(),e.strokeStyle="rgba(220, 255, 220, 0.7)";for(let l=0;l<12;l++){const h=-a*.35+l/11*a*.7,f=Math.sin(n*.01+l*.6+o.wailPhase)*a*.5,b=Math.cos(n*.008+l*.4)*a*.3;e.lineWidth=a*(.04+l%3*.02),e.beginPath(),e.moveTo(h,-a*1.1),e.bezierCurveTo(h+f*.3,-a*.5,h+f*.6+b*.5,a*.2,h+f+b,a*1+l%2*a*.5),e.stroke()}const g=Math.sin(n*.01+o.wailPhase)*a*.3;e.fillStyle="rgba(230, 255, 230, 0.8)",e.strokeStyle="rgba(180, 230, 180, 0.6)",e.lineWidth=a*.02,e.save(),e.translate(-a*.6,-a*.2),e.rotate(-.3+g*.002),e.beginPath(),e.ellipse(0,0,a*.15,a*.25,0,0,Math.PI*2),e.fill(),e.stroke();for(let l=0;l<4;l++){const h=-a*.08+l*a*.055;e.beginPath(),e.moveTo(h,a*.15),e.lineTo(h,a*.35),e.stroke()}e.restore(),e.save(),e.translate(a*.6,-a*.2),e.rotate(.3-g*.002),e.beginPath(),e.ellipse(0,0,a*.15,a*.25,0,0,Math.PI*2),e.fill(),e.stroke();for(let l=0;l<4;l++){const h=-a*.08+l*a*.055;e.beginPath(),e.moveTo(h,a*.15),e.lineTo(h,a*.35),e.stroke()}e.restore(),e.strokeStyle="rgba(180, 255, 180, 0.3)",e.lineWidth=a*.02;for(let l=0;l<6;l++){const h=l/6*Math.PI*2,f=Math.sin(n*.01+l*.8)*a*.6,b=a*(1.5+l%2*.5);e.beginPath(),e.moveTo(0,a*.4),e.bezierCurveTo(Math.cos(h)*a*.8+f*.5,a*1,Math.cos(h)*a*1.5+f,a*1.8,Math.cos(h)*b+f*1.5,a*2.5),e.stroke()}e.restore()},drawMoon(e,o,n){const t=o.x,r=o.y,a=o.size;e.save(),e.globalAlpha=o.opacity,e.translate(t,r);const s=e.createRadialGradient(-a*.2,-a*.2,0,0,0,a);s.addColorStop(0,"#90EE90"),s.addColorStop(.5,"#7FD87F"),s.addColorStop(1,"#5CB85C"),e.fillStyle=s,e.beginPath(),e.arc(0,0,a,0,Math.PI*2),e.fill(),e.fillStyle="rgba(60, 130, 60, 0.3)",e.beginPath(),e.arc(-a*.3,-a*.2,a*.15,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(a*.25,a*.1,a*.2,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(a*.1,-a*.4,a*.12,0,Math.PI*2),e.fill(),e.save(),e.translate(a*.2,a*.35),e.scale(.15,.15),e.fillStyle="rgba(34, 139, 34, 0.4)";for(let M=0;M<3;M++){const y=M/3*Math.PI*2-Math.PI/2;e.save(),e.rotate(y),e.translate(0,-a*.7),e.beginPath(),e.moveTo(0,a*.3),e.bezierCurveTo(-a*.5,-a*.2,-a*.3,-a*.6,0,-a*.35),e.bezierCurveTo(a*.3,-a*.6,a*.5,-a*.2,0,a*.3),e.closePath(),e.fill(),e.restore()}e.restore();const d=.3+Math.sin(n*.002+o.glowPhase)*.15;e.globalAlpha=d;const i=e.createRadialGradient(0,0,a*.8,0,0,a*1.8);i.addColorStop(0,"rgba(144, 238, 144, 0.8)"),i.addColorStop(.5,"rgba(144, 238, 144, 0.4)"),i.addColorStop(1,"rgba(144, 238, 144, 0)"),e.fillStyle=i,e.beginPath(),e.arc(0,0,a*1.8,0,Math.PI*2),e.fill(),e.restore()},drawStaticLeprechaun:w,drawTwinklingStar(e,o,n){const t=o.x,r=o.y,a=o.size,s=.5+Math.sin(n*o.twinkleSpeed+o.twinklePhase)*.5,d=`rgba(220, 255, 220, ${s})`,i=`rgba(180, 255, 180, ${s*.4})`;e.save(),e.translate(t,r),e.shadowColor=i,e.shadowBlur=a*3*s,e.fillStyle=d,e.beginPath();for(let M=0;M<4;M++){const y=M*Math.PI/2,P=Math.cos(y)*a,g=Math.sin(y)*a,l=y+Math.PI/4,h=Math.cos(l)*(a*.3),f=Math.sin(l)*(a*.3);M===0?e.moveTo(P,g):e.lineTo(P,f),e.lineTo(h,f)}e.closePath(),e.fill(),e.shadowBlur=a*2*s,e.beginPath(),e.arc(0,0,a*.25,0,Math.PI*2),e.fill(),e.restore()}};
|
|
1
|
+
import{drawMooningLeprechaun as b}from"./mooning-drawing.js";import{spawnMooningCharacter as w,updateMooningCharacter as S}from"./mooning-antics.js";import{drawLeprechaun as p,drawStaticLeprechaun as v}from"./character-drawing.js";export default{name:"st-patricks",displayName:"St Patrick's Day",emoji:"\u2618\uFE0F",intensityConfig:{light:{count:40,speedRange:[.4,1.2],sizeRange:[2,4],pots:1,leprechaunChance:3e-4,twinklingStars:10},medium:{count:80,speedRange:[.5,1.5],sizeRange:[2,5],pots:2,leprechaunChance:5e-4,twinklingStars:18},heavy:{count:120,speedRange:[.6,1.8],sizeRange:[3,6],pots:3,leprechaunChance:8e-4,twinklingStars:25}},particles:["clover-petal","shamrock","gold-coin","sparkle"],decorations:["pot-of-gold","rainbow","leprechaun","harp","static-leprechaun","moon","twinkling-star","mooning-leprechaun"],optInDecorations:{mooningLeprechaun:{chance:"mooningLeprechaunChance",value:5e-4}},colors:{primary:"#228B22",secondary:"#90EE90",white:"#FFFFFF",accent:"#FFD700",rainbow:["#ff0000","#ffa500","#ffff00","#00ff00","#0000ff","#4b0082","#8b00ff"]},createCloverPetal(e,o,n){const t=["#228B22","#32CD32","#90EE90","#3CB371","#2E8B57"];return{type:"clover-petal",x:-30,y:Math.random()*o,vx:n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]),size:(n.sizeRange[0]+Math.random()*(n.sizeRange[1]-n.sizeRange[0]))*1.5,speed:(Math.random()-.5)*.2,opacity:.75+Math.random()*.25,windOffset:Math.random()*Math.PI*2,windSpeed:.015+Math.random()*.025,rotation:Math.random()*Math.PI*2,rotationSpeed:(Math.random()-.5)*.03,color:t[Math.floor(Math.random()*t.length)],flutter:Math.random()*Math.PI*2,flutterSpeed:.02+Math.random()*.02,active:!0}},createShamrock(e,o,n){const t=Math.random();let r,a;return t<.5?(r="#228B22",a="#006400"):t<.75?(r="#FFFFFF",a="#d0d0d0"):(r="#FFD700",a="#DAA520"),{type:"shamrock",x:-30,y:Math.random()*o,vx:n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]),size:n.sizeRange[0]+Math.random()*(n.sizeRange[1]-n.sizeRange[0]),speed:(Math.random()-.5)*.15,opacity:.7+Math.random()*.3,windOffset:Math.random()*Math.PI*2,windSpeed:.015+Math.random()*.025,rotation:Math.random()*Math.PI*2,rotationSpeed:(Math.random()-.5)*.02,color:r,strokeColor:a,active:!0}},createGoldCoin(e,o,n){return{type:"gold-coin",x:-30,y:Math.random()*o,vx:(n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]))*.8,size:2+Math.random()*3,speed:(Math.random()-.5)*.1,opacity:.9+Math.random()*.1,rotation:0,rotationSpeed:.05+Math.random()*.1,spinPhase:Math.random()*Math.PI*2,glintPhase:Math.random()*Math.PI*2,windOffset:Math.random()*Math.PI*2,windSpeed:.01+Math.random()*.02,active:!0}},createPotOfGold(e,o,n={}){return{type:"pot-of-gold",x:n.x!==void 0?n.x:Math.random()*e*.6+e*.2,y:n.y!==void 0?n.y:o-40,size:20+Math.random()*10,opacity:1,glintPhase:Math.random()*Math.PI*2,coins:[],active:!0,static:!0}},createRainbow(e,o,n,t){const a=n,s=(20+n)/2,i=o*.2,h=t-i/2;return{type:"rainbow",x:s,y:h,startX:20,endX:a,endY:t,width:e,height:o,opacity:.7,vx:0,vy:0,active:!0,static:!0}},createSparkle(e,o,n){const t=["#FFD700","#FFFFFF","#228B22"];return{type:"sparkle",x:-20,y:Math.random()*o,vx:(n.speedRange[0]+Math.random()*(n.speedRange[1]-n.speedRange[0]))*.8,size:n.sizeRange[0]+Math.random()*(n.sizeRange[1]-n.sizeRange[0])*.6,vy:(Math.random()-.5)*.2,opacity:.6+Math.random()*.4,rotation:Math.random()*Math.PI*2,rotationSpeed:(Math.random()-.5)*.04,color:t[Math.floor(Math.random()*t.length)],twinklePhase:Math.random()*Math.PI*2,windOffset:Math.random()*Math.PI*2,windSpeed:.015+Math.random()*.02,active:!0,static:!1}},createFallingParticle(e,o,n){const t=Math.random();return t<.6?this.createCloverPetal(e,o,n):t<.8?this.createShamrock(e,o,n):t<.95?this.createGoldCoin(e,o,n):this.createSparkle(e,o,n)},createInitialDecorations(e,o,n){const t=[];t.push({type:"moon",x:e-120,y:100,size:60+Math.random()*20,opacity:.85,glowPhase:Math.random()*Math.PI*2,active:!0,static:!0});const r=20,a=220,s=o-40;t.push(this.createPotOfGold(e,o,{x:a,y:s})),t.push(this.createRainbow(e,o,a,s));const i=1+Math.floor(Math.random()*2);for(let M=0;M<i;M++){const y=60+Math.random()*(e-120);t.push({type:"static-leprechaun",x:y,y:o-35,size:12+Math.random()*4,opacity:1,time:Math.random()*1e3,wavePhase:Math.random()*Math.PI*2,active:!0,static:!0})}const h=n.twinklingStars??18;for(let M=0;M<h;M++)t.push(this.createTwinklingStar(e,o));return t},createTwinklingStar(e,o){return{type:"twinkling-star",x:Math.random()*e,y:Math.random()*(o*.5),size:1+Math.random()*2,opacity:.6+Math.random()*.3,twinklePhase:Math.random()*Math.PI*2,twinkleSpeed:.003+Math.random()*.003,active:!0,static:!0}},spawnSpecialParticle(e,o,n,t){const r=w(e,o,n,t,"mooning-leprechaun","mooningLeprechaunChance");if(r)return r;const a=Math.random();if(a<t.leprechaunChance){if(e.some(M=>M.type==="leprechaun"))return null;const s=Math.random()<.5,i=(o+100)/180;return{type:"leprechaun",x:s?-50:o+50,y:n-35,baseY:n-35,vx:s?i:-i,vy:0,size:12+Math.random()*6,opacity:1,time:0,legPhase:Math.random()*Math.PI*2,active:!0,static:!1}}if(a<t.leprechaunChance+3e-4){if(e.some(i=>i.type==="banshee"))return null;const s=Math.random()<.5;return{type:"banshee",x:s?-100:o+100,y:Math.random()*(n*.4)+50,baseY:Math.random()*(n*.4)+50,vx:s?2+Math.random()*1:-(2+Math.random()*1),size:18+Math.random()*10,opacity:.6+Math.random()*.2,waveAmplitude:25+Math.random()*20,waveFrequency:.0015+Math.random()*.002,waveOffset:Math.random()*Math.PI*2,time:0,wailPhase:Math.random()*Math.PI*2,active:!0,static:!1}}if(a<.001&&e.filter(i=>i.type==="pot-of-gold").length<t.pots){const i=this.createPotOfGold(o,n),h=this.createRainbow(o,n,i.x,i.y);return e.push(h),i}return null},updateSpecialParticles(e,o,n,t){return e.forEach(r=>{if(r.active){if(r.type==="mooning-leprechaun"){S(r,o,n);return}(r.type==="leprechaun"||r.type==="banshee")&&(r.time+=o,r.type==="leprechaun"?(r.vx>0&&r.x>n+r.size*2||r.vx<0&&r.x<-r.size*2)&&(r.active=!1):r.type==="banshee"&&(r.vx>0&&r.x>n+r.size*4||r.vx<0&&r.x<-r.size*4)&&(r.active=!1)),r.type==="static-leprechaun"&&(r.time+=o)}}),e.filter(r=>r.active)},drawCloverPetal(e,o,n){const t=o.x,r=o.y,a=o.size,s=Math.sin(n*o.flutterSpeed+o.flutter)*.3;e.save(),e.globalAlpha=o.opacity,e.translate(t,r),e.rotate(o.rotation+s),e.fillStyle=o.color,e.strokeStyle="#006400",e.lineWidth=a*.08,e.beginPath(),e.moveTo(0,-a*.3),e.bezierCurveTo(-a*.6,-a*.7,-a*.8,-a*.3,-a*.5,a*.2),e.lineTo(0,a*.8),e.lineTo(a*.5,a*.2),e.bezierCurveTo(a*.8,-a*.3,a*.6,-a*.7,0,-a*.3),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="rgba(0, 100, 0, 0.4)",e.lineWidth=a*.06,e.beginPath(),e.moveTo(0,-a*.2),e.lineTo(0,a*.7),e.stroke(),e.restore()},drawSparkle(e,o,n){const t=o.x,r=o.y,a=o.size,s=.6+Math.sin(n*.004+o.twinklePhase)*.4;e.save(),e.globalAlpha=o.opacity*s,e.translate(t,r),e.rotate(o.rotation),e.fillStyle=o.color,e.shadowColor=o.color,e.shadowBlur=a*2,e.beginPath(),e.moveTo(0,-a),e.lineTo(a*.3,-a*.3),e.lineTo(a,0),e.lineTo(a*.3,a*.3),e.lineTo(0,a),e.lineTo(-a*.3,a*.3),e.lineTo(-a,0),e.lineTo(-a*.3,-a*.3),e.closePath(),e.fill(),e.restore()},drawShamrock(e,o){const n=o.x,t=o.y,r=o.size;e.save(),e.globalAlpha=o.opacity,e.translate(n,t),e.rotate(o.rotation),e.fillStyle=o.color||"#228B22",e.strokeStyle=o.strokeColor||"#006400",e.lineWidth=r*.1;for(let a=0;a<3;a++){const s=a/3*Math.PI*2-Math.PI/2;e.save(),e.rotate(s),e.translate(0,-r*.7),e.beginPath(),e.moveTo(0,r*.3),e.bezierCurveTo(-r*.5,-r*.2,-r*.3,-r*.6,0,-r*.35),e.bezierCurveTo(r*.3,-r*.6,r*.5,-r*.2,0,r*.3),e.closePath(),e.fill(),e.stroke(),e.restore()}e.strokeStyle="#8B4513",e.lineWidth=r*.15,e.beginPath(),e.moveTo(0,0),e.lineTo(0,r*1.2),e.stroke(),e.restore()},drawGoldCoin(e,o,n){const t=o.x,r=o.y,a=o.size,s=Math.abs(Math.cos(o.rotation))*a;if(e.save(),e.globalAlpha=o.opacity,e.translate(t,r),e.fillStyle="#FFD700",e.strokeStyle="#DAA520",e.lineWidth=a*.15,e.beginPath(),e.ellipse(0,0,s,a,0,0,Math.PI*2),e.fill(),e.stroke(),s>a*.3){const i=(Math.sin(n*.005+o.glintPhase)+1)*.5;e.fillStyle=`rgba(255, 255, 255, ${i*.6})`,e.beginPath(),e.ellipse(-s*.3,-a*.3,s*.3,a*.3,0,0,Math.PI*2),e.fill()}e.restore()},drawPotOfGold(e,o,n){const t=o.x,r=o.y,a=o.size;e.save(),e.translate(t,r),e.fillStyle="#1a1a1a",e.strokeStyle="#0a0a0a",e.lineWidth=2,e.beginPath(),e.arc(0,0,a*.7,0,Math.PI),e.lineTo(-a*.7,a*.4),e.quadraticCurveTo(0,a*.6,a*.7,a*.4),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="#2a2a2a",e.lineWidth=3,e.beginPath(),e.arc(0,-a*.2,a*.5,Math.PI,0),e.stroke();const s=[{x:-a*.4,y:-a*.2,s:.8},{x:a*.3,y:-a*.3,s:.9},{x:-a*.1,y:-a*.5,s:1},{x:a*.5,y:-a*.1,s:.7},{x:-a*.5,y:0,s:.75},{x:a*.1,y:-a*.6,s:.85},{x:0,y:-a*.3,s:.95}];e.fillStyle="#FFD700",e.strokeStyle="#DAA520",e.lineWidth=1,s.forEach((h,M)=>{const y=n*.002+M*.5,P=(Math.sin(y)+1)*.5;e.save(),e.translate(h.x,h.y),e.beginPath(),e.arc(0,0,a*.2*h.s,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle=`rgba(255, 255, 255, ${P*.6})`,e.beginPath(),e.arc(-a*.08*h.s,-a*.08*h.s,a*.08*h.s,0,Math.PI*2),e.fill(),e.restore()}),e.globalAlpha=.3;const i=e.createRadialGradient(0,-a*.2,0,0,-a*.2,a*1.5);i.addColorStop(0,"#FFD700"),i.addColorStop(1,"rgba(255, 215, 0, 0)"),e.fillStyle=i,e.fillRect(-a*1.5,-a*1.5,a*3,a*1.5),e.restore()},drawSmallGoldPot(e,o,n,t){e.save(),e.translate(o,n),e.fillStyle="#1a1a1a",e.strokeStyle="#0a0a0a",e.lineWidth=1,e.beginPath(),e.arc(0,-t*.4,t*.7,0,Math.PI,!0),e.lineTo(t*.7,t*.4),e.quadraticCurveTo(0,t*.6,-t*.7,t*.4),e.closePath(),e.fill(),e.stroke(),e.fillStyle="#FFD700",e.beginPath(),e.arc(0,-t*.7,t*.5,0,Math.PI*2),e.fill(),e.restore()},drawRainbow(e,o){const n=o.startX,t=o.endX,r=o.endY,a=o.height;e.save(),e.globalAlpha=.5;const s=Math.abs(t-n),i=(n+t)/2,h=a*.2,M=(s*s+4*h*h)/(8*h),y=r+h-M,P=this.colors.rainbow,u=5;for(let d=P.length-1;d>=0;d--)e.strokeStyle=P[d],e.lineWidth=u,e.beginPath(),e.arc(i,y,M+(P.length-1-d)*u,Math.PI,0),e.stroke();e.restore(),this.drawSmallGoldPot(e,t,r,20)},drawLeprechaun:p,drawMooningLeprechaun:b,drawBanshee(e,o,n){const t=o.x,r=o.y+Math.sin(n*o.waveFrequency+o.waveOffset)*o.waveAmplitude,a=o.size,s=o.vx>0?1:-1;e.save(),e.globalAlpha=o.opacity,e.translate(t,r),s===-1&&e.scale(-1,1);const i=e.createRadialGradient(0,-a*.5,0,0,-a*.5,a*3);i.addColorStop(0,"rgba(180, 255, 180, 0.4)"),i.addColorStop(.3,"rgba(150, 255, 150, 0.2)"),i.addColorStop(.6,"rgba(120, 255, 120, 0.1)"),i.addColorStop(1,"rgba(100, 255, 100, 0)"),e.fillStyle=i,e.fillRect(-a*3,-a*3,a*6,a*6);const h=Math.sin(n*.008+o.wailPhase)*a*.25;e.fillStyle="rgba(200, 240, 200, 0.3)",e.beginPath(),e.moveTo(-a*.8,-a*1.2),e.bezierCurveTo(-a*1.2+h*2,-a*.4,-a*1+h*1.5,a*.6,-a*.6+h,a*1.8),e.bezierCurveTo(-a*.3,a*1.5,a*.3,a*1.5,a*.6-h,a*1.8),e.bezierCurveTo(a*1-h*1.5,a*.6,a*1.2-h*2,-a*.4,a*.8,-a*1.2),e.closePath(),e.fill(),e.fillStyle="rgba(230, 255, 230, 0.85)",e.strokeStyle="rgba(180, 255, 180, 0.6)",e.lineWidth=a*.04,e.beginPath(),e.moveTo(-a*.5,-a*1);for(let l=0;l<=12;l++){const d=l/12,f=Math.sin(n*.012+d*Math.PI*3+o.wailPhase)*a*.3,g=(d-.5)*a*1.8+f,m=-a*1+d*a*2.2;l===0?e.moveTo(g,m):e.lineTo(g,m)}e.lineTo(-a*.5,-a*1),e.closePath(),e.fill(),e.stroke(),e.strokeStyle="rgba(180, 255, 180, 0.4)",e.lineWidth=a*.02;for(let l=0;l<8;l++){const d=-a*.7+l*(a*1.4/7),f=Math.sin(n*.015+l*.8)*a*.2;e.beginPath(),e.moveTo(d,a*.4),e.lineTo(d+f,a*1.2+l%2*a*.4),e.stroke()}const M=Math.sin(n*.006)*.15;e.save(),e.rotate(M),e.fillStyle="rgba(240, 255, 240, 0.9)",e.strokeStyle="rgba(180, 230, 180, 0.5)",e.lineWidth=a*.03,e.beginPath(),e.ellipse(0,-a*.8,a*.35,a*.55,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="rgba(50, 100, 50, 0.6)",e.beginPath(),e.ellipse(-a*.15,-a*.85,a*.12,a*.08,0,0,Math.PI*2),e.ellipse(a*.15,-a*.85,a*.12,a*.08,0,0,Math.PI*2),e.fill();const y=.6+Math.sin(n*.008+o.wailPhase)*.4;e.fillStyle=`rgba(100, 255, 150, ${y})`,e.shadowColor="#64ff96",e.shadowBlur=a*.6,e.beginPath(),e.arc(-a*.15,-a*.85,a*.08,0,Math.PI*2),e.arc(a*.15,-a*.85,a*.08,0,Math.PI*2),e.fill(),e.shadowBlur=0;const P=.2+Math.sin(n*.01+o.wailPhase)*.1;e.fillStyle="rgba(80, 150, 80, 0.7)",e.strokeStyle="rgba(50, 100, 50, 0.8)",e.lineWidth=a*.02,e.beginPath(),e.ellipse(0,-a*.6,a*.15,a*(.2+P),0,0,Math.PI*2),e.fill(),e.stroke(),e.restore(),e.strokeStyle="rgba(220, 255, 220, 0.7)";for(let l=0;l<12;l++){const d=-a*.35+l/11*a*.7,f=Math.sin(n*.01+l*.6+o.wailPhase)*a*.5,g=Math.cos(n*.008+l*.4)*a*.3;e.lineWidth=a*(.04+l%3*.02),e.beginPath(),e.moveTo(d,-a*1.1),e.bezierCurveTo(d+f*.3,-a*.5,d+f*.6+g*.5,a*.2,d+f+g,a*1+l%2*a*.5),e.stroke()}const u=Math.sin(n*.01+o.wailPhase)*a*.3;e.fillStyle="rgba(230, 255, 230, 0.8)",e.strokeStyle="rgba(180, 230, 180, 0.6)",e.lineWidth=a*.02,e.save(),e.translate(-a*.6,-a*.2),e.rotate(-.3+u*.002),e.beginPath(),e.ellipse(0,0,a*.15,a*.25,0,0,Math.PI*2),e.fill(),e.stroke();for(let l=0;l<4;l++){const d=-a*.08+l*a*.055;e.beginPath(),e.moveTo(d,a*.15),e.lineTo(d,a*.35),e.stroke()}e.restore(),e.save(),e.translate(a*.6,-a*.2),e.rotate(.3-u*.002),e.beginPath(),e.ellipse(0,0,a*.15,a*.25,0,0,Math.PI*2),e.fill(),e.stroke();for(let l=0;l<4;l++){const d=-a*.08+l*a*.055;e.beginPath(),e.moveTo(d,a*.15),e.lineTo(d,a*.35),e.stroke()}e.restore(),e.strokeStyle="rgba(180, 255, 180, 0.3)",e.lineWidth=a*.02;for(let l=0;l<6;l++){const d=l/6*Math.PI*2,f=Math.sin(n*.01+l*.8)*a*.6,g=a*(1.5+l%2*.5);e.beginPath(),e.moveTo(0,a*.4),e.bezierCurveTo(Math.cos(d)*a*.8+f*.5,a*1,Math.cos(d)*a*1.5+f,a*1.8,Math.cos(d)*g+f*1.5,a*2.5),e.stroke()}e.restore()},drawMoon(e,o,n){const t=o.x,r=o.y,a=o.size;e.save(),e.globalAlpha=o.opacity,e.translate(t,r);const s=e.createRadialGradient(-a*.2,-a*.2,0,0,0,a);s.addColorStop(0,"#90EE90"),s.addColorStop(.5,"#7FD87F"),s.addColorStop(1,"#5CB85C"),e.fillStyle=s,e.beginPath(),e.arc(0,0,a,0,Math.PI*2),e.fill(),e.fillStyle="rgba(60, 130, 60, 0.3)",e.beginPath(),e.arc(-a*.3,-a*.2,a*.15,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(a*.25,a*.1,a*.2,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(a*.1,-a*.4,a*.12,0,Math.PI*2),e.fill(),e.save(),e.translate(a*.2,a*.35),e.scale(.15,.15),e.fillStyle="rgba(34, 139, 34, 0.4)";for(let M=0;M<3;M++){const y=M/3*Math.PI*2-Math.PI/2;e.save(),e.rotate(y),e.translate(0,-a*.7),e.beginPath(),e.moveTo(0,a*.3),e.bezierCurveTo(-a*.5,-a*.2,-a*.3,-a*.6,0,-a*.35),e.bezierCurveTo(a*.3,-a*.6,a*.5,-a*.2,0,a*.3),e.closePath(),e.fill(),e.restore()}e.restore();const i=.3+Math.sin(n*.002+o.glowPhase)*.15;e.globalAlpha=i;const h=e.createRadialGradient(0,0,a*.8,0,0,a*1.8);h.addColorStop(0,"rgba(144, 238, 144, 0.8)"),h.addColorStop(.5,"rgba(144, 238, 144, 0.4)"),h.addColorStop(1,"rgba(144, 238, 144, 0)"),e.fillStyle=h,e.beginPath(),e.arc(0,0,a*1.8,0,Math.PI*2),e.fill(),e.restore()},drawStaticLeprechaun:v,drawTwinklingStar(e,o,n){const t=o.x,r=o.y,a=o.size,s=.5+Math.sin(n*o.twinkleSpeed+o.twinklePhase)*.5,i=`rgba(220, 255, 220, ${s})`,h=`rgba(180, 255, 180, ${s*.4})`;e.save(),e.translate(t,r),e.shadowColor=h,e.shadowBlur=a*3*s,e.fillStyle=i,e.beginPath();for(let M=0;M<4;M++){const y=M*Math.PI/2,P=Math.cos(y)*a,u=Math.sin(y)*a,l=y+Math.PI/4,d=Math.cos(l)*(a*.3),f=Math.sin(l)*(a*.3);M===0?e.moveTo(P,u):e.lineTo(P,f),e.lineTo(d,f)}e.closePath(),e.fill(),e.shadowBlur=a*2*s,e.beginPath(),e.arc(0,0,a*.25,0,Math.PI*2),e.fill(),e.restore()}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CELEBRATION_THEMES={christmas:{label:"Christmas",traits:{snowflake:{label:"Snowflakes",types:["snowflake"],kind:"particle"},tree:{label:"Christmas trees",types:["tree"],count:"trees"},wreath:{label:"Wreaths",types:["wreath"],count:"wreaths"},snowman:{label:"Snowmen",types:["snowman"],count:"snowmen"},northStar:{label:"North star",types:["north-star"],count:"northStars"},sleigh:{label:"Santa's sleigh",types:["sleigh"]},robin:{label:"Robins",types:["robin"]},train:{label:"Steam train",types:["train"]},elf:{label:"Elves",types:["elf"]},peeingElf:{label:"Peeing elf",types:["peeing-elf"],chance:"peeingElfChance",enabled:!1},thievingElf:{label:"Thieving elf",types:["thieving-elf"],chance:"thievingElfChance",enabled:!1},firework:{label:"Fireworks",types:["firework","spark"]}}},halloween:{label:"Halloween",traits:{pumpkin:{label:"Falling pumpkins",types:["pumpkin"],kind:"particle"},bat:{label:"Bats",types:["bat"],kind:"particle"},ghost:{label:"Ghosts",types:["ghost"],kind:"particle"},spider:{label:"Spiders",types:["spider","spider-small"],count:"spiders"},gravestone:{label:"Gravestones",types:["gravestone"],count:"gravestones"},jackOLantern:{label:"Jack-o-lanterns",types:["jack-o-lantern"],count:"jackOLanterns"},scarecrow:{label:"Scarecrows",types:["scarecrow"],count:"scarecrows"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},hauntedHouse:{label:"Haunted house",types:["haunted-house"]},moon:{label:"Moon",types:["moon"]},witch:{label:"Witches",types:["witch"]},cauldron:{label:"Cauldron",types:["cauldron"]},floatingPumpkin:{label:"Floating pumpkins",types:["floating-pumpkin"]},lightning:{label:"Lightning",global:!0}}},valentines:{label:"Valentine's Day",traits:{heart:{label:"Hearts",types:["heart"],kind:"particle"},rosePetal:{label:"Rose petals",types:["rose-petal"],kind:"particle"},sparkle:{label:"Sparkles",types:["sparkle"],kind:"particle"},lips:{label:"Kisses",types:["lips"],kind:"particle"},heartGarland:{label:"Heart garlands",types:["heart-garland"],count:"garlands"},butterfly:{label:"Butterflies",types:["butterfly"],count:"butterflies"},envelope:{label:"Envelopes",types:["envelope"],count:"envelopes"},heartMoon:{label:"Heart moon",types:["heart-moon"]},cupid:{label:"Cupid",types:["cupid"],chance:"cupidChance"},loveLetter:{label:"Love letters",types:["love-letter"]},neonSign:{label:"Neon sign",types:["neon-sign"]}}},"guy-fawkes":{label:"Guy Fawkes Night",traits:{ember:{label:"Embers",types:["ember"],kind:"particle"},spark:{label:"Sparks",types:["spark","trail"],kind:"particle"},bonfire:{label:"Bonfires",types:["bonfire"],count:"bonfires"},guyEffigy:{label:"Guy effigies",types:["guy-effigy"]},catherineWheel:{label:"Catherine wheels",types:["catherine-wheel"]},romanCandle:{label:"Roman candles",types:["roman-candle"]},sparklerBundle:{label:"Sparklers",types:["sparkler-bundle"]},firework:{label:"Fireworks",types:["firework"],chance:"fireworkChance"},rocket:{label:"Rockets",types:["rocket"],chance:"rocketChance"},burst:{label:"Bursts",types:["burst"],chance:"burstChance"},moon:{label:"Moon",types:["moon"]},lightning:{label:"Lightning",global:!0}}},"st-patricks":{label:"St Patrick's Day",traits:{cloverPetal:{label:"Clover petals",types:["clover-petal"],kind:"particle"},shamrock:{label:"Shamrocks",types:["shamrock"],kind:"particle"},goldCoin:{label:"Gold coins",types:["gold-coin"],kind:"particle"},sparkle:{label:"Sparkles",types:["sparkle"],kind:"particle"},potOfGold:{label:"Pots of gold",types:["pot-of-gold"],count:"pots"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},leprechaun:{label:"Leprechauns",types:["leprechaun","static-leprechaun"],chance:"leprechaunChance"},rainbow:{label:"Rainbow",types:["rainbow"]},banshee:{label:"Banshee",types:["banshee"]},moon:{label:"Moon",types:["moon"]}}},"st-davids":{label:"St David's Day",traits:{daffodilPetal:{label:"Daffodil petals",types:["daffodil-petal"],kind:"particle"},daffodil:{label:"Daffodils",types:["daffodil"],kind:"particle"},springSparkle:{label:"Spring sparkles",types:["spring-sparkle"],kind:"particle"},leek:{label:"Leeks",types:["leek"],kind:"particle"},daffodilField:{label:"Daffodil fields",types:["daffodil-field"],count:"daffodilFields"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},welshDragon:{label:"Welsh dragon",types:["welsh-dragon"],chance:"dragonChance"},flag:{label:"Welsh flag",types:["flag"]},harp:{label:"Harp",types:["harp"]},leekBundle:{label:"Leek bundles",types:["leek-bundle"]}}},"st-georges":{label:"St George's Day",traits:{rosePetal:{label:"Rose petals",types:["rose-petal"],kind:"particle"},tudorRose:{label:"Tudor roses",types:["tudor-rose"],kind:"particle"},oakLeaf:{label:"Oak leaves",types:["oak-leaf"],kind:"particle"},sparkle:{label:"Sparkles",types:["sparkle"],kind:"particle"},englishRose:{label:"English roses",types:["english-rose"],count:"roses"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},knight:{label:"Knight",types:["knight"],chance:"knightChance"},dragon:{label:"Dragon",types:["dragon"]},castle:{label:"Castle",types:["castle"]},shield:{label:"Shields",types:["shield"]},stGeorgesCross:{label:"St George's cross",types:["st-georges-cross"]}}},"st-andrews":{label:"St Andrew's Day",traits:{heatherPetal:{label:"Heather petals",types:["heather-petal"],kind:"particle"},heather:{label:"Heather",types:["heather"],kind:"particle"},saltireSparkle:{label:"Saltire sparkles",types:["saltire-sparkle"],kind:"particle"},thistle:{label:"Thistles",types:["thistle"],kind:"particle"},thistlePlant:{label:"Thistle plants",types:["thistle-plant"],count:"thistles"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},bagpiper:{label:"Bagpiper",types:["bagpiper"],chance:"bagpiperChance"},saltireFlag:{label:"Saltire flag",types:["saltire-flag"]},tartanPattern:{label:"Tartan",types:["tartan-pattern"]},highlandScene:{label:"Highland scene",types:["highland-scene"]}}}};const c=a=>String(a).toLowerCase().replace(/[^a-z0-9]/g,"");export const traitAttribute=a=>a.replace(/[A-Z]/g,s=>"-"+s.toLowerCase());export function resolveCelebrationTraits(a,s={}){const n=new Set,r=new Set,i={},o=Object.entries(s).map(([l,e])=>[c(l),String(e).trim().toLowerCase()]),b=o.some(([l,e])=>["fallingonly","snowonly"].includes(l)&&["true","1","yes","on"].includes(e));for(const[l,e]of Object.entries(CELEBRATION_THEMES[a]?.traits||{})){const p=o.find(([t])=>[l,e.count,e.chance].filter(Boolean).some(
|
|
1
|
+
export const CELEBRATION_THEMES={christmas:{label:"Christmas",traits:{snowflake:{label:"Snowflakes",types:["snowflake"],kind:"particle"},tree:{label:"Christmas trees",types:["tree"],count:"trees"},wreath:{label:"Wreaths",types:["wreath"],count:"wreaths"},snowman:{label:"Snowmen",types:["snowman"],count:"snowmen"},northStar:{label:"North star",types:["north-star"],count:"northStars"},sleigh:{label:"Santa's sleigh",types:["sleigh"]},robin:{label:"Robins",types:["robin"]},train:{label:"Steam train",types:["train"]},elf:{label:"Elves",types:["elf"]},peeingElf:{label:"Peeing elf",types:["peeing-elf"],chance:"peeingElfChance",enabled:!1},thievingElf:{label:"Thieving elf",types:["thieving-elf"],chance:"thievingElfChance",enabled:!1},firework:{label:"Fireworks",types:["firework","spark"]},mooningElf:{label:"Mooning elf",types:["mooning-elf"],chance:"mooningElfChance",enabled:!1}}},halloween:{label:"Halloween",traits:{pumpkin:{label:"Falling pumpkins",types:["pumpkin"],kind:"particle"},bat:{label:"Bats",types:["bat"],kind:"particle"},ghost:{label:"Ghosts",types:["ghost"],kind:"particle"},spider:{label:"Spiders",types:["spider","spider-small"],count:"spiders"},gravestone:{label:"Gravestones",types:["gravestone"],count:"gravestones"},jackOLantern:{label:"Jack-o-lanterns",types:["jack-o-lantern"],count:"jackOLanterns"},scarecrow:{label:"Scarecrows",types:["scarecrow"],count:"scarecrows"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},hauntedHouse:{label:"Haunted house",types:["haunted-house"]},moon:{label:"Moon",types:["moon"]},witch:{label:"Witches",types:["witch"]},cauldron:{label:"Cauldron",types:["cauldron"]},floatingPumpkin:{label:"Floating pumpkins",types:["floating-pumpkin"]},lightning:{label:"Lightning",global:!0}}},valentines:{label:"Valentine's Day",traits:{heart:{label:"Hearts",types:["heart"],kind:"particle"},rosePetal:{label:"Rose petals",types:["rose-petal"],kind:"particle"},sparkle:{label:"Sparkles",types:["sparkle"],kind:"particle"},lips:{label:"Kisses",types:["lips"],kind:"particle"},heartGarland:{label:"Heart garlands",types:["heart-garland"],count:"garlands"},butterfly:{label:"Butterflies",types:["butterfly"],count:"butterflies"},envelope:{label:"Envelopes",types:["envelope"],count:"envelopes"},heartMoon:{label:"Heart moon",types:["heart-moon"]},cupid:{label:"Cupid",types:["cupid"],chance:"cupidChance"},loveLetter:{label:"Love letters",types:["love-letter"]},neonSign:{label:"Neon sign",types:["neon-sign"]}}},"guy-fawkes":{label:"Guy Fawkes Night",traits:{ember:{label:"Embers",types:["ember"],kind:"particle"},spark:{label:"Sparks",types:["spark","trail"],kind:"particle"},bonfire:{label:"Bonfires",types:["bonfire"],count:"bonfires"},guyEffigy:{label:"Guy effigies",types:["guy-effigy"]},catherineWheel:{label:"Catherine wheels",types:["catherine-wheel"]},romanCandle:{label:"Roman candles",types:["roman-candle"]},sparklerBundle:{label:"Sparklers",types:["sparkler-bundle"]},firework:{label:"Fireworks",types:["firework"],chance:"fireworkChance"},rocket:{label:"Rockets",types:["rocket"],chance:"rocketChance"},burst:{label:"Bursts",types:["burst"],chance:"burstChance"},moon:{label:"Moon",types:["moon"]},lightning:{label:"Lightning",global:!0}}},"st-patricks":{label:"St Patrick's Day",traits:{cloverPetal:{label:"Clover petals",types:["clover-petal"],kind:"particle"},shamrock:{label:"Shamrocks",types:["shamrock"],kind:"particle"},goldCoin:{label:"Gold coins",types:["gold-coin"],kind:"particle"},sparkle:{label:"Sparkles",types:["sparkle"],kind:"particle"},potOfGold:{label:"Pots of gold",types:["pot-of-gold"],count:"pots"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},leprechaun:{label:"Leprechauns",types:["leprechaun","static-leprechaun"],chance:"leprechaunChance"},rainbow:{label:"Rainbow",types:["rainbow"]},banshee:{label:"Banshee",types:["banshee"]},moon:{label:"Moon",types:["moon"]},mooningLeprechaun:{label:"Mooning leprechaun",types:["mooning-leprechaun"],chance:"mooningLeprechaunChance",enabled:!1}}},"st-davids":{label:"St David's Day",traits:{daffodilPetal:{label:"Daffodil petals",types:["daffodil-petal"],kind:"particle"},daffodil:{label:"Daffodils",types:["daffodil"],kind:"particle"},springSparkle:{label:"Spring sparkles",types:["spring-sparkle"],kind:"particle"},leek:{label:"Leeks",types:["leek"],kind:"particle"},daffodilField:{label:"Daffodil fields",types:["daffodil-field"],count:"daffodilFields"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},welshDragon:{label:"Welsh dragon",types:["welsh-dragon"],chance:"dragonChance"},flag:{label:"Welsh flag",types:["flag"]},harp:{label:"Harp",types:["harp"]},leekBundle:{label:"Leek bundles",types:["leek-bundle"]}}},"st-georges":{label:"St George's Day",traits:{rosePetal:{label:"Rose petals",types:["rose-petal"],kind:"particle"},tudorRose:{label:"Tudor roses",types:["tudor-rose"],kind:"particle"},oakLeaf:{label:"Oak leaves",types:["oak-leaf"],kind:"particle"},sparkle:{label:"Sparkles",types:["sparkle"],kind:"particle"},englishRose:{label:"English roses",types:["english-rose"],count:"roses"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},knight:{label:"Knight",types:["knight"],chance:"knightChance"},dragon:{label:"Dragon",types:["dragon"]},castle:{label:"Castle",types:["castle"]},shield:{label:"Shields",types:["shield"]},stGeorgesCross:{label:"St George's cross",types:["st-georges-cross"]}}},"st-andrews":{label:"St Andrew's Day",traits:{heatherPetal:{label:"Heather petals",types:["heather-petal"],kind:"particle"},heather:{label:"Heather",types:["heather"],kind:"particle"},saltireSparkle:{label:"Saltire sparkles",types:["saltire-sparkle"],kind:"particle"},thistle:{label:"Thistles",types:["thistle"],kind:"particle"},thistlePlant:{label:"Thistle plants",types:["thistle-plant"],count:"thistles"},twinklingStar:{label:"Twinkling stars",types:["twinkling-star"],count:"twinklingStars"},bagpiper:{label:"Bagpiper",types:["bagpiper"],chance:"bagpiperChance"},saltireFlag:{label:"Saltire flag",types:["saltire-flag"]},tartanPattern:{label:"Tartan",types:["tartan-pattern"]},highlandScene:{label:"Highland scene",types:["highland-scene"]}}}};const c=a=>String(a).toLowerCase().replace(/[^a-z0-9]/g,"");export const traitAttribute=a=>a.replace(/[A-Z]/g,s=>"-"+s.toLowerCase());export function resolveCelebrationTraits(a,s={}){const n=new Set,r=new Set,i={},o=Object.entries(s).map(([l,e])=>[c(l),String(e).trim().toLowerCase()]),b=o.some(([l,e])=>["fallingonly","snowonly"].includes(l)&&["true","1","yes","on"].includes(e));for(const[l,e]of Object.entries(CELEBRATION_THEMES[a]?.traits||{})){const p=o.find(([t])=>[l,e.count,e.chance].filter(Boolean).some(h=>c(h)===t))?.[1];if(["off","false","0","no"].includes(p)||e.enabled===!1&&!["true","1","yes","on"].includes(p)||b&&e.kind!=="particle"){for(const t of e.types||[])n.add(t);e.global&&r.add(l);for(const t of[e.count,e.chance].filter(Boolean))i[t]=0}}return{disabledTypes:n,disabledGlobals:r,config:i}}
|
package/public/js/effects.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){"use strict";fetch("/api/effects/settings").then(function(
|
|
1
|
+
(function(){"use strict";fetch("/api/effects/settings").then(function(b){return b.json()}).then(function(b){var z=b.respectMotion!==!1,A=b.defaultDuration||600,S=b.defaultAnimation||"fade",E=b.defaultThreshold!=null?b.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 n(t,e,i){var a=r(t,e);return a!==null?parseFloat(a):i}function v(t,e,i){var a=r(t,e);return a===null?i:a==="true"||a==="1"}function l(t,e,i){var a=new IntersectionObserver(function(o){o[0].isIntersecting&&(a.unobserve(t),i())},{threshold:e});a.observe(t)}d.querySelectorAll(".dm-fx-reveal").forEach(function(t){var e=r(t,"animation")||S,i=n(t,"duration",A),a=n(t,"delay",0),o=n(t,"threshold",E),u=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 "+i+"ms ease "+a+"ms, transform "+i+"ms ease "+a+"ms";var s=new IntersectionObserver(function(f){f.forEach(function(y){y.isIntersecting?(t.style.opacity="1",t.style.transform="",u&&s.unobserve(t)):u||(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=n(t,"to",0),i=n(t,"from",0),a=n(t,"duration",2e3),o=r(t,"prefix")||"",u=r(t,"suffix")||"",s=n(t,"decimals",0),f=r(t,"separator")||"";function y(h){var c=h.toFixed(s);if(f){var x=c.split(".");x[0]=x[0].replace(/\B(?=(\d{3})+(?!\d))/g,f),c=x.join(".")}return o+c+u}if(p){t.textContent=y(e);return}l(t,.5,function(){var h=null;requestAnimationFrame(function c(x){h||(h=x);var w=Math.min((x-h)/a,1),q=1-Math.pow(1-w,3);t.textContent=y(i+(e-i)*q),w<1&&requestAnimationFrame(c)})})}),p||d.querySelectorAll(".dm-fx-scramble").forEach(function(t){var e=n(t,"speed",50),i=t.textContent,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&",o=0;l(t,.3,function(){var u=setInterval(function(){for(var s="",f=0;f<i.length;f++)f<o||i[f]===" "?s+=i[f]:s+=a[Math.floor(Math.random()*a.length)];t.textContent=s,o<i.length?o++:clearInterval(u)},e)})}),p||d.querySelectorAll(".dm-fx-shake").forEach(function(t){var e=n(t,"intensity",5),i=n(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:i,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 i=r(t,"colour")||"rgba(255,255,255,0.3)",a=n(t,"duration",600),o=t.getBoundingClientRect(),u=Math.max(o.width,o.height)*2,s=document.createElement("span");s.style.cssText="position:absolute;border-radius:50%;pointer-events:none;width:"+u+"px;height:"+u+"px;left:"+(e.clientX-o.left-u/2)+"px;top:"+(e.clientY-o.top-u/2)+"px;background:"+i+";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 m=window.Domma&&window.Domma.effects;m&&(d.querySelectorAll(".dm-fx-breathe").forEach(function(t){if(!p){var e={};r(t,"amplitude")&&(e.amplitude=n(t,"amplitude",6)),r(t,"duration")&&(e.duration=n(t,"duration",3e3)),r(t,"easing")&&(e.easing=r(t,"easing")),r(t,"stagger")&&(e.stagger=n(t,"stagger",0)),m.breathe(t,e)}}),d.querySelectorAll(".dm-fx-pulse").forEach(function(t){if(!p){var e={};r(t,"scale")&&(e.scale=n(t,"scale",1.05)),r(t,"duration")&&(e.duration=n(t,"duration",2e3)),r(t,"easing")&&(e.easing=r(t,"easing")),m.pulse(t,e)}}),d.querySelectorAll(".dm-fx-scribe").forEach(function(t){var e={};r(t,"speed")&&(e.speed=n(t,"speed",50)),r(t,"delete-speed")&&(e.deleteSpeed=n(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=n(t,"loop-delay",1e3)),r(t,"pause-on-hover")&&(e.pauseOnHover=v(t,"pause-on-hover",!1));var i=r(t,"actions");if(i)try{e.actions=JSON.parse(i)}catch{return}else{var a=t.textContent.trim();if(!a)return;t.textContent="",e.actions=[{render:a}]}m.scribe(t,e)}),d.querySelectorAll(".dm-fx-twinkle").forEach(function(t){var e={};r(t,"count")&&(e.count=n(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=n(t,"min-size",1)),r(t,"max-size")&&(e.maxSize=n(t,"max-size",3)),m.twinkle(t,e)}),typeof m.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=n(t,"density",50)),r(t,"speed")&&(e.speed=n(t,"speed",1)),r(t,"sway")&&(e.sway=n(t,"sway",60)),r(t,"rotation-speed")&&(e.rotationSpeed=n(t,"rotation-speed",6)),r(t,"min-width")&&(e.minWidth=n(t,"min-width",5)),r(t,"max-width")&&(e.maxWidth=n(t,"max-width",9)),r(t,"min-height")&&(e.minHeight=n(t,"min-height",12)),r(t,"max-height")&&(e.maxHeight=n(t,"max-height",22)),r(t,"fade-start")&&(e.fadeStart=n(t,"fade-start",.55)),r(t,"burst")&&(e.burst=v(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=n(t,"burst-count",150)),r(t,"z-index")&&(e.zIndex=n(t,"z-index",1));var i=r(t,"mode");i==="page"?m.tickerTape(null,e):m.tickerTape(t,e)}}),typeof m.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=n(t,"density",18)),r(t,"speed")&&(e.speed=n(t,"speed",1)),r(t,"wander")&&(e.wander=n(t,"wander",1)),r(t,"rise-speed")&&(e.riseSpeed=n(t,"rise-speed",.4)),r(t,"flap-speed")&&(e.flapSpeed=n(t,"flap-speed",1)),r(t,"min-size")&&(e.minSize=n(t,"min-size",14)),r(t,"max-size")&&(e.maxSize=n(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=n(t,"burst-count",40)),r(t,"z-index")&&(e.zIndex=n(t,"z-index",1));var i=r(t,"mode");i==="page"?m.butterflies(null,e):m.butterflies(t,e)}}),typeof m.strobe=="function"&&d.querySelectorAll(".dm-fx-strobe").forEach(function(t){if(p)return;function e(s){var f=r(t,s);if(!f)return null;var y=f.split(",").map(function(h){return h.trim()}).filter(Boolean);return y.length?y:null}var i={};r(t,"preset")&&(i.preset=r(t,"preset")),r(t,"motion")&&(i.motion=r(t,"motion")),r(t,"speed")&&(i.speed=n(t,"speed",1)),r(t,"sweep-arc")&&(i.sweepArc=n(t,"sweep-arc",100)),r(t,"beam-width")&&(i.beamWidth=n(t,"beam-width",16)),r(t,"intensity")&&(i.intensity=n(t,"intensity",.7)),r(t,"flicker")&&(i.flicker=v(t,"flicker",!0)),r(t,"hz")&&(i.hz=n(t,"hz",10));var a=e("origins");a&&(i.origins=a);var o=e("colours");o&&(i.colours=o),r(t,"duration")&&(i.duration=n(t,"duration",null)),r(t,"z-index")&&(i.zIndex=n(t,"z-index",9999));var u=r(t,"mode");u==="page"?m.strobe(null,i):m.strobe(t,i)}));var g=d.querySelectorAll(".dm-fx-celebrate");g.length&&import("/public/js/celebrations/index.js?v=20261003-characters").then(function(t){var e=t.CelebrationsEffect;g.forEach(function(i){var a=r(i,"character");if(a){if(i.querySelector("canvas"))return;t.mountCharacter(i,{character:a,size:r(i,"size")||"medium",facing:r(i,"facing")||"right",animate:!p&&v(i,"animate",!0)}).then(function(c){var x=new MutationObserver(function(){i.isConnected||(c.destroy(),x.disconnect())});i.isConnected?x.observe(document.body,{childList:!0,subtree:!0}):c.destroy()}).catch(function(c){console.error("[celebrate]",c)});return}if(!p){var o=i.getAttribute("data-fx-theme")||"auto",u=i.getAttribute("data-fx-intensity")||"medium",s=i.getAttribute("data-fx-z-index")?parseInt(i.getAttribute("data-fx-z-index"),10):999;if(!(o==="auto"&&(o=e.getCurrentTheme(),!o))){var f={},y=["data-fx-theme","data-fx-intensity","data-fx-z-index"];Array.from(i.attributes).forEach(function(c){c.name.startsWith("data-fx-")&&!y.includes(c.name)&&(f[c.name.slice(8)]=c.value)});var h=new e({theme:o,intensity:u,enabled:!0,zIndex:s,overrides:f});h.init().then(function(){h.start()})}}})}).catch(function(){})}).catch(function(){})})();
|
|
@@ -16,6 +16,7 @@ import {buildMenuNav} from './menuRender.js';
|
|
|
16
16
|
import {checkVisibility} from '../middleware/auth.js';
|
|
17
17
|
import {getRoleHierarchy, getRoleLevel} from './roles.js';
|
|
18
18
|
import {buildTextStyle} from '../../public/js/text-style.mjs';
|
|
19
|
+
import {CHARACTERS, resolveCharacterSize} from '../../public/js/celebrations/character-catalog.mjs';
|
|
19
20
|
import {publicButtonSettings} from '../../public/js/form-submit.mjs';
|
|
20
21
|
|
|
21
22
|
const __dirname_md = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -1762,11 +1763,26 @@ function processEffectsBlocks(markdown) {
|
|
|
1762
1763
|
return `<span class="dm-fx-counter"${dataAttrs}>${prefix}${to}${suffix}</span>`;
|
|
1763
1764
|
});
|
|
1764
1765
|
|
|
1765
|
-
|
|
1766
|
+
function renderCelebration(attrStr) {
|
|
1766
1767
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
1768
|
+
if (attrs.character) {
|
|
1769
|
+
if (!Object.hasOwn(CHARACTERS, attrs.character)) {
|
|
1770
|
+
return `<span class="dm-fx-character-error">Unknown celebration character: ${escapeHtmlText(attrs.character)}</span>`;
|
|
1771
|
+
}
|
|
1772
|
+
const size = resolveCharacterSize(attrs.size);
|
|
1773
|
+
const [left, top, right, bottom] = CHARACTERS[attrs.character].bounds;
|
|
1774
|
+
const width = Math.ceil(size * (right - left) / (bottom - top));
|
|
1775
|
+
const dataAttrs = ['character', 'size', 'animate', 'facing'].filter(key => attrs[key] !== undefined)
|
|
1776
|
+
.map(key => ` data-fx-${key}="${escapeAttr(String(attrs[key]))}"`).join('');
|
|
1777
|
+
return `<div class="dm-fx-celebrate dm-fx-character"${dataAttrs} style="display:inline-block;max-width:100%;width:${width}px;vertical-align:bottom"></div>`;
|
|
1778
|
+
}
|
|
1767
1779
|
const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${escapeAttr(String(v))}"`).join('');
|
|
1768
1780
|
return `<div class="dm-fx-celebrate"${dataAttrs}></div>`;
|
|
1769
|
-
}
|
|
1781
|
+
}
|
|
1782
|
+
apply('celebrate', renderCelebration);
|
|
1783
|
+
// A single inline character also accepts the concise, unpaired form.
|
|
1784
|
+
result = result.replace(/\[celebrate([^\]]*)\]/gi, (match, attrs) =>
|
|
1785
|
+
parseShortcodeAttrs(attrs).character ? renderCelebration(attrs) : match);
|
|
1770
1786
|
|
|
1771
1787
|
// Named celebration shortcodes - sugar for [celebrate theme="..."] with override support
|
|
1772
1788
|
['christmas', 'halloween', 'st-patricks', 'valentines', 'guy-fawkes',
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
<script src="/public/js/forms.js?v=20260927-submit-btn" type="module"></script>
|
|
156
156
|
|
|
157
157
|
<!-- Core effects runtime -->
|
|
158
|
-
<script src="/public/js/effects.js?v=
|
|
158
|
+
<script src="/public/js/effects.js?v=20261003-characters"></script>
|
|
159
159
|
|
|
160
160
|
<!-- Plugin body-end injection -->
|
|
161
161
|
{{bodyEndInject}}
|