masoneffect 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,924 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { createEventDispatcher, onMount, onDestroy } from "svelte";
5
+ function noop() {
6
+ }
7
+ function run(fn) {
8
+ return fn();
9
+ }
10
+ function blank_object() {
11
+ return /* @__PURE__ */ Object.create(null);
12
+ }
13
+ function run_all(fns) {
14
+ fns.forEach(run);
15
+ }
16
+ function is_function(thing) {
17
+ return typeof thing === "function";
18
+ }
19
+ function safe_not_equal(a, b) {
20
+ return a != a ? b == b : a !== b || a && typeof a === "object" || typeof a === "function";
21
+ }
22
+ function is_empty(obj) {
23
+ return Object.keys(obj).length === 0;
24
+ }
25
+ function insert(target, node, anchor) {
26
+ target.insertBefore(node, anchor || null);
27
+ }
28
+ function detach(node) {
29
+ if (node.parentNode) {
30
+ node.parentNode.removeChild(node);
31
+ }
32
+ }
33
+ function element(name) {
34
+ return document.createElement(name);
35
+ }
36
+ function attr(node, attribute, value) {
37
+ if (value == null) node.removeAttribute(attribute);
38
+ else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
39
+ }
40
+ function children(element2) {
41
+ return Array.from(element2.childNodes);
42
+ }
43
+ let current_component;
44
+ function set_current_component(component) {
45
+ current_component = component;
46
+ }
47
+ const dirty_components = [];
48
+ const binding_callbacks = [];
49
+ let render_callbacks = [];
50
+ const flush_callbacks = [];
51
+ const resolved_promise = /* @__PURE__ */ Promise.resolve();
52
+ let update_scheduled = false;
53
+ function schedule_update() {
54
+ if (!update_scheduled) {
55
+ update_scheduled = true;
56
+ resolved_promise.then(flush);
57
+ }
58
+ }
59
+ function add_render_callback(fn) {
60
+ render_callbacks.push(fn);
61
+ }
62
+ const seen_callbacks = /* @__PURE__ */ new Set();
63
+ let flushidx = 0;
64
+ function flush() {
65
+ if (flushidx !== 0) {
66
+ return;
67
+ }
68
+ const saved_component = current_component;
69
+ do {
70
+ try {
71
+ while (flushidx < dirty_components.length) {
72
+ const component = dirty_components[flushidx];
73
+ flushidx++;
74
+ set_current_component(component);
75
+ update(component.$$);
76
+ }
77
+ } catch (e) {
78
+ dirty_components.length = 0;
79
+ flushidx = 0;
80
+ throw e;
81
+ }
82
+ set_current_component(null);
83
+ dirty_components.length = 0;
84
+ flushidx = 0;
85
+ while (binding_callbacks.length) binding_callbacks.pop()();
86
+ for (let i = 0; i < render_callbacks.length; i += 1) {
87
+ const callback = render_callbacks[i];
88
+ if (!seen_callbacks.has(callback)) {
89
+ seen_callbacks.add(callback);
90
+ callback();
91
+ }
92
+ }
93
+ render_callbacks.length = 0;
94
+ } while (dirty_components.length);
95
+ while (flush_callbacks.length) {
96
+ flush_callbacks.pop()();
97
+ }
98
+ update_scheduled = false;
99
+ seen_callbacks.clear();
100
+ set_current_component(saved_component);
101
+ }
102
+ function update($$) {
103
+ if ($$.fragment !== null) {
104
+ $$.update();
105
+ run_all($$.before_update);
106
+ const dirty = $$.dirty;
107
+ $$.dirty = [-1];
108
+ $$.fragment && $$.fragment.p($$.ctx, dirty);
109
+ $$.after_update.forEach(add_render_callback);
110
+ }
111
+ }
112
+ function flush_render_callbacks(fns) {
113
+ const filtered = [];
114
+ const targets = [];
115
+ render_callbacks.forEach((c) => fns.indexOf(c) === -1 ? filtered.push(c) : targets.push(c));
116
+ targets.forEach((c) => c());
117
+ render_callbacks = filtered;
118
+ }
119
+ const outroing = /* @__PURE__ */ new Set();
120
+ function transition_in(block, local) {
121
+ if (block && block.i) {
122
+ outroing.delete(block);
123
+ block.i(local);
124
+ }
125
+ }
126
+ function mount_component(component, target, anchor) {
127
+ const { fragment, after_update } = component.$$;
128
+ fragment && fragment.m(target, anchor);
129
+ add_render_callback(() => {
130
+ const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
131
+ if (component.$$.on_destroy) {
132
+ component.$$.on_destroy.push(...new_on_destroy);
133
+ } else {
134
+ run_all(new_on_destroy);
135
+ }
136
+ component.$$.on_mount = [];
137
+ });
138
+ after_update.forEach(add_render_callback);
139
+ }
140
+ function destroy_component(component, detaching) {
141
+ const $$ = component.$$;
142
+ if ($$.fragment !== null) {
143
+ flush_render_callbacks($$.after_update);
144
+ run_all($$.on_destroy);
145
+ $$.fragment && $$.fragment.d(detaching);
146
+ $$.on_destroy = $$.fragment = null;
147
+ $$.ctx = [];
148
+ }
149
+ }
150
+ function make_dirty(component, i) {
151
+ if (component.$$.dirty[0] === -1) {
152
+ dirty_components.push(component);
153
+ schedule_update();
154
+ component.$$.dirty.fill(0);
155
+ }
156
+ component.$$.dirty[i / 31 | 0] |= 1 << i % 31;
157
+ }
158
+ function init(component, options, instance, create_fragment2, not_equal, props, append_styles = null, dirty = [-1]) {
159
+ const parent_component = current_component;
160
+ set_current_component(component);
161
+ const $$ = component.$$ = {
162
+ fragment: null,
163
+ ctx: [],
164
+ // state
165
+ props,
166
+ update: noop,
167
+ not_equal,
168
+ bound: blank_object(),
169
+ // lifecycle
170
+ on_mount: [],
171
+ on_destroy: [],
172
+ on_disconnect: [],
173
+ before_update: [],
174
+ after_update: [],
175
+ context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
176
+ // everything else
177
+ callbacks: blank_object(),
178
+ dirty,
179
+ skip_bound: false,
180
+ root: options.target || parent_component.$$.root
181
+ };
182
+ append_styles && append_styles($$.root);
183
+ let ready = false;
184
+ $$.ctx = instance ? instance(component, options.props || {}, (i, ret, ...rest) => {
185
+ const value = rest.length ? rest[0] : ret;
186
+ if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
187
+ if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
188
+ if (ready) make_dirty(component, i);
189
+ }
190
+ return ret;
191
+ }) : [];
192
+ $$.update();
193
+ ready = true;
194
+ run_all($$.before_update);
195
+ $$.fragment = create_fragment2 ? create_fragment2($$.ctx) : false;
196
+ if (options.target) {
197
+ if (options.hydrate) {
198
+ const nodes = children(options.target);
199
+ $$.fragment && $$.fragment.l(nodes);
200
+ nodes.forEach(detach);
201
+ } else {
202
+ $$.fragment && $$.fragment.c();
203
+ }
204
+ if (options.intro) transition_in(component.$$.fragment);
205
+ mount_component(component, options.target, options.anchor);
206
+ flush();
207
+ }
208
+ set_current_component(parent_component);
209
+ }
210
+ class SvelteComponent {
211
+ constructor() {
212
+ /**
213
+ * ### PRIVATE API
214
+ *
215
+ * Do not use, may change at any time
216
+ *
217
+ * @type {any}
218
+ */
219
+ __publicField(this, "$$");
220
+ /**
221
+ * ### PRIVATE API
222
+ *
223
+ * Do not use, may change at any time
224
+ *
225
+ * @type {any}
226
+ */
227
+ __publicField(this, "$$set");
228
+ }
229
+ /** @returns {void} */
230
+ $destroy() {
231
+ destroy_component(this, 1);
232
+ this.$destroy = noop;
233
+ }
234
+ /**
235
+ * @template {Extract<keyof Events, string>} K
236
+ * @param {K} type
237
+ * @param {((e: Events[K]) => void) | null | undefined} callback
238
+ * @returns {() => void}
239
+ */
240
+ $on(type, callback) {
241
+ if (!is_function(callback)) {
242
+ return noop;
243
+ }
244
+ const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []);
245
+ callbacks.push(callback);
246
+ return () => {
247
+ const index = callbacks.indexOf(callback);
248
+ if (index !== -1) callbacks.splice(index, 1);
249
+ };
250
+ }
251
+ /**
252
+ * @param {Partial<Props>} props
253
+ * @returns {void}
254
+ */
255
+ $set(props) {
256
+ if (this.$$set && !is_empty(props)) {
257
+ this.$$.skip_bound = true;
258
+ this.$$set(props);
259
+ this.$$.skip_bound = false;
260
+ }
261
+ }
262
+ }
263
+ const PUBLIC_VERSION = "4";
264
+ if (typeof window !== "undefined")
265
+ (window.__svelte || (window.__svelte = { v: /* @__PURE__ */ new Set() })).v.add(PUBLIC_VERSION);
266
+ function debounce(func, wait) {
267
+ let timeout = null;
268
+ return function executedFunction(...args) {
269
+ const later = () => {
270
+ timeout = null;
271
+ func.apply(this, args);
272
+ };
273
+ if (timeout !== null) {
274
+ clearTimeout(timeout);
275
+ }
276
+ timeout = setTimeout(later, wait);
277
+ };
278
+ }
279
+ class MasonEffect {
280
+ constructor(container, options = {}) {
281
+ this.container = typeof container === "string" ? document.querySelector(container) : container;
282
+ if (!this.container) {
283
+ throw new Error("Container element not found");
284
+ }
285
+ this.config = {
286
+ text: options.text || "mason effect",
287
+ densityStep: options.densityStep ?? 2,
288
+ maxParticles: options.maxParticles ?? 3200,
289
+ pointSize: options.pointSize ?? 0.5,
290
+ ease: options.ease ?? 0.05,
291
+ repelRadius: options.repelRadius ?? 150,
292
+ repelStrength: options.repelStrength ?? 1,
293
+ particleColor: options.particleColor || "#fff",
294
+ fontFamily: options.fontFamily || "Inter, system-ui, Arial",
295
+ fontSize: options.fontSize || null,
296
+ width: options.width || null,
297
+ height: options.height || null,
298
+ devicePixelRatio: options.devicePixelRatio ?? null,
299
+ onReady: options.onReady || null,
300
+ onUpdate: options.onUpdate || null
301
+ };
302
+ this.canvas = document.createElement("canvas");
303
+ const ctx = this.canvas.getContext("2d");
304
+ if (!ctx) {
305
+ throw new Error("Canvas context not available");
306
+ }
307
+ this.ctx = ctx;
308
+ this.container.appendChild(this.canvas);
309
+ this.canvas.style.display = "block";
310
+ this.offCanvas = document.createElement("canvas");
311
+ const offCtx = this.offCanvas.getContext("2d");
312
+ if (!offCtx) {
313
+ throw new Error("Offscreen canvas context not available");
314
+ }
315
+ this.offCtx = offCtx;
316
+ this.W = 0;
317
+ this.H = 0;
318
+ this.DPR = this.config.devicePixelRatio || Math.min(window.devicePixelRatio || 1, 1.8);
319
+ this.particles = [];
320
+ this.mouse = { x: 0, y: 0, down: false };
321
+ this.animationId = null;
322
+ this.isRunning = false;
323
+ this.isVisible = false;
324
+ this.intersectionObserver = null;
325
+ this.debounceDelay = options.debounceDelay ?? 150;
326
+ const boundHandleResize = this.handleResize.bind(this);
327
+ this.handleResize = debounce(boundHandleResize, this.debounceDelay);
328
+ this.handleMouseMove = this.handleMouseMove.bind(this);
329
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
330
+ this.handleMouseDown = this.handleMouseDown.bind(this);
331
+ this.handleMouseUp = this.handleMouseUp.bind(this);
332
+ this._debouncedMorph = debounce(this._morphInternal.bind(this), this.debounceDelay);
333
+ this._debouncedUpdateConfig = debounce(this._updateConfigInternal.bind(this), this.debounceDelay);
334
+ this.init();
335
+ }
336
+ init() {
337
+ this.resize();
338
+ this.setupEventListeners();
339
+ this.setupIntersectionObserver();
340
+ if (this.config.onReady) {
341
+ this.config.onReady(this);
342
+ }
343
+ }
344
+ setupIntersectionObserver() {
345
+ if (typeof window === "undefined" || typeof window.IntersectionObserver === "undefined") {
346
+ this.isVisible = true;
347
+ this.start();
348
+ return;
349
+ }
350
+ if (this.intersectionObserver) {
351
+ return;
352
+ }
353
+ this.intersectionObserver = new IntersectionObserver(
354
+ (entries) => {
355
+ for (const entry of entries) {
356
+ if (entry.target !== this.container) continue;
357
+ if (entry.isIntersecting) {
358
+ this.isVisible = true;
359
+ this.start();
360
+ } else {
361
+ this.isVisible = false;
362
+ this.stop();
363
+ }
364
+ }
365
+ },
366
+ {
367
+ threshold: 0.1
368
+ // 10% 이상 보일 때 동작
369
+ }
370
+ );
371
+ this.intersectionObserver.observe(this.container);
372
+ }
373
+ resize() {
374
+ const width = this.config.width || this.container.clientWidth || window.innerWidth;
375
+ const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
376
+ if (width <= 0 || height <= 0) {
377
+ return;
378
+ }
379
+ this.W = Math.floor(width * this.DPR);
380
+ this.H = Math.floor(height * this.DPR);
381
+ const MAX_CANVAS_SIZE = 4096;
382
+ if (this.W > MAX_CANVAS_SIZE || this.H > MAX_CANVAS_SIZE) {
383
+ const scale = Math.min(MAX_CANVAS_SIZE / this.W, MAX_CANVAS_SIZE / this.H);
384
+ this.W = Math.floor(this.W * scale);
385
+ this.H = Math.floor(this.H * scale);
386
+ this.DPR = this.DPR * scale;
387
+ }
388
+ this.canvas.width = this.W;
389
+ this.canvas.height = this.H;
390
+ this.canvas.style.width = width + "px";
391
+ this.canvas.style.height = height + "px";
392
+ if (this.W > 0 && this.H > 0) {
393
+ this.buildTargets();
394
+ if (!this.particles.length) {
395
+ this.initParticles();
396
+ }
397
+ }
398
+ }
399
+ /**
400
+ * 텍스트가 영역 안에 들어가는지 확인하는 헬퍼 함수 (줄바꿈 지원)
401
+ * @param fontSize 확인할 폰트 크기
402
+ * @param text 텍스트 (\n으로 줄바꿈 구분)
403
+ * @param maxWidth 최대 너비
404
+ * @param maxHeight 최대 높이
405
+ * @returns { width: number, height: number, fits: boolean }
406
+ */
407
+ measureTextFit(fontSize, text, maxWidth, maxHeight) {
408
+ this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;
409
+ const lines = text.split("\n");
410
+ const lineHeight = fontSize;
411
+ const lineSpacing = fontSize * 0.1;
412
+ const spacing = fontSize * 0.05;
413
+ let maxLineWidth = 0;
414
+ for (const line of lines) {
415
+ if (line.length === 0) continue;
416
+ const textWidth = this.offCtx.measureText(line).width;
417
+ const totalWidth = textWidth + spacing * (line.length > 0 ? line.length - 1 : 0);
418
+ maxLineWidth = Math.max(maxLineWidth, totalWidth);
419
+ }
420
+ const totalHeight = lines.length > 0 ? lineHeight * lines.length + lineSpacing * (lines.length - 1) : lineHeight;
421
+ return {
422
+ width: maxLineWidth,
423
+ height: totalHeight,
424
+ fits: maxLineWidth <= maxWidth && totalHeight <= maxHeight
425
+ };
426
+ }
427
+ /**
428
+ * 이진 검색을 사용하여 적절한 폰트 크기를 찾는 최적화된 함수
429
+ * 반복 횟수를 O(log n)으로 줄여 성능 개선 (최대 15회 반복, 기존 최대 100회에서 대폭 감소)
430
+ */
431
+ findOptimalFontSize(text, maxWidth, maxHeight, initialFontSize) {
432
+ const minFontSize = 12;
433
+ const initialMeasure = this.measureTextFit(initialFontSize, text, maxWidth, maxHeight);
434
+ if (initialMeasure.fits) {
435
+ return initialFontSize;
436
+ }
437
+ if (initialFontSize <= minFontSize) {
438
+ return minFontSize;
439
+ }
440
+ let low = minFontSize;
441
+ let high = initialFontSize;
442
+ let bestSize = minFontSize;
443
+ while (low <= high) {
444
+ const mid = Math.floor((low + high) / 2);
445
+ const measure = this.measureTextFit(mid, text, maxWidth, maxHeight);
446
+ if (measure.fits) {
447
+ bestSize = mid;
448
+ low = mid + 1;
449
+ } else {
450
+ high = mid - 1;
451
+ }
452
+ }
453
+ return bestSize;
454
+ }
455
+ buildTargets() {
456
+ if (this.W <= 0 || this.H <= 0) {
457
+ return;
458
+ }
459
+ const text = this.config.text;
460
+ this.offCanvas.width = this.W;
461
+ this.offCanvas.height = this.H;
462
+ this.offCtx.clearRect(0, 0, this.offCanvas.width, this.offCanvas.height);
463
+ const base = Math.min(this.W, this.H);
464
+ const initialFontSize = this.config.fontSize || Math.max(80, Math.floor(base * 0.18));
465
+ const padding = 40;
466
+ const maxWidth = this.W - padding * 2;
467
+ const maxHeight = this.H - padding * 2;
468
+ const fontSize = this.findOptimalFontSize(text, maxWidth, maxHeight, initialFontSize);
469
+ this.offCtx.fillStyle = "#ffffff";
470
+ this.offCtx.textAlign = "center";
471
+ this.offCtx.textBaseline = "middle";
472
+ this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;
473
+ const lines = text.split("\n");
474
+ const lineHeight = fontSize;
475
+ const lineSpacing = fontSize * 0.1;
476
+ const spacing = fontSize * 0.05;
477
+ const totalTextHeight = lines.length > 0 ? lineHeight * lines.length + lineSpacing * (lines.length - 1) : lineHeight;
478
+ let startY = this.H / 2 - totalTextHeight / 2 + lineHeight / 2;
479
+ for (const line of lines) {
480
+ if (line.length === 0) {
481
+ startY += lineHeight + lineSpacing;
482
+ continue;
483
+ }
484
+ const chars = line.split("");
485
+ const totalWidth = this.offCtx.measureText(line).width + spacing * (chars.length - 1);
486
+ let x = this.W / 2 - totalWidth / 2;
487
+ for (const ch of chars) {
488
+ this.offCtx.fillText(ch, x + this.offCtx.measureText(ch).width / 2, startY);
489
+ x += this.offCtx.measureText(ch).width + spacing;
490
+ }
491
+ startY += lineHeight + lineSpacing;
492
+ }
493
+ const step = Math.max(2, this.config.densityStep);
494
+ const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;
495
+ const targets = [];
496
+ for (let y = 0; y < this.H; y += step) {
497
+ for (let x = 0; x < this.W; x += step) {
498
+ const i = (y * this.W + x) * 4;
499
+ if (img[i] + img[i + 1] + img[i + 2] > 600) {
500
+ targets.push({ x, y });
501
+ }
502
+ }
503
+ }
504
+ while (targets.length > this.config.maxParticles) {
505
+ targets.splice(Math.floor(Math.random() * targets.length), 1);
506
+ }
507
+ if (this.particles.length < targets.length) {
508
+ const need = targets.length - this.particles.length;
509
+ for (let i = 0; i < need; i++) {
510
+ this.particles.push(this.makeParticle());
511
+ }
512
+ } else if (this.particles.length > targets.length) {
513
+ this.particles.length = targets.length;
514
+ }
515
+ for (let i = 0; i < this.particles.length; i++) {
516
+ const p = this.particles[i];
517
+ const t = targets[i];
518
+ p.tx = t.x;
519
+ p.ty = t.y;
520
+ }
521
+ }
522
+ makeParticle() {
523
+ const sx = Math.random() * this.W;
524
+ const sy = Math.random() * this.H;
525
+ return {
526
+ x: sx,
527
+ y: sy,
528
+ vx: 0,
529
+ vy: 0,
530
+ tx: sx,
531
+ ty: sy,
532
+ initialX: sx,
533
+ // 초기 위치 저장 (scatter 시 돌아갈 위치)
534
+ initialY: sy,
535
+ j: Math.random() * Math.PI * 2
536
+ };
537
+ }
538
+ initParticles() {
539
+ for (const p of this.particles) {
540
+ const sx = Math.random() * this.W;
541
+ const sy = Math.random() * this.H;
542
+ p.x = sx;
543
+ p.y = sy;
544
+ p.vx = p.vy = 0;
545
+ p.initialX = sx;
546
+ p.initialY = sy;
547
+ }
548
+ }
549
+ scatter() {
550
+ for (const p of this.particles) {
551
+ if (p.initialX !== void 0 && p.initialY !== void 0) {
552
+ p.tx = p.initialX;
553
+ p.ty = p.initialY;
554
+ } else {
555
+ p.initialX = p.x;
556
+ p.initialY = p.y;
557
+ p.tx = p.initialX;
558
+ p.ty = p.initialY;
559
+ }
560
+ }
561
+ }
562
+ morph(textOrOptions) {
563
+ this._debouncedMorph(textOrOptions);
564
+ }
565
+ _morphInternal(textOrOptions) {
566
+ if (this.W === 0 || this.H === 0) {
567
+ this.resize();
568
+ }
569
+ if (typeof textOrOptions === "string") {
570
+ this.config.text = textOrOptions;
571
+ this.buildTargets();
572
+ } else if (textOrOptions && typeof textOrOptions === "object") {
573
+ const needsRebuild = textOrOptions.text !== void 0;
574
+ this.config = { ...this.config, ...textOrOptions };
575
+ if (needsRebuild) {
576
+ this.buildTargets();
577
+ }
578
+ } else {
579
+ this.buildTargets();
580
+ }
581
+ }
582
+ update() {
583
+ this.ctx.clearRect(0, 0, this.W, this.H);
584
+ for (const p of this.particles) {
585
+ let ax = (p.tx - p.x) * this.config.ease;
586
+ let ay = (p.ty - p.y) * this.config.ease;
587
+ if (this.mouse.x || this.mouse.y) {
588
+ const dx = p.x - this.mouse.x;
589
+ const dy = p.y - this.mouse.y;
590
+ const d2 = dx * dx + dy * dy;
591
+ const r2 = this.config.repelRadius * this.DPR;
592
+ if (d2 < r2 * r2) {
593
+ const d = Math.sqrt(d2) + 1e-4;
594
+ const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r2);
595
+ ax += dx / d * f * 6;
596
+ ay += dy / d * f * 6;
597
+ }
598
+ }
599
+ p.j += 2;
600
+ ax += Math.cos(p.j) * 0.05;
601
+ ay += Math.sin(p.j * 1.3) * 0.05;
602
+ p.vx = (p.vx + ax) * Math.random();
603
+ p.vy = (p.vy + ay) * Math.random();
604
+ p.x += p.vx;
605
+ p.y += p.vy;
606
+ }
607
+ this.ctx.fillStyle = this.config.particleColor;
608
+ const r = this.config.pointSize * this.DPR;
609
+ for (const p of this.particles) {
610
+ this.ctx.beginPath();
611
+ this.ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
612
+ this.ctx.fill();
613
+ }
614
+ if (this.config.onUpdate) {
615
+ this.config.onUpdate(this);
616
+ }
617
+ }
618
+ animate() {
619
+ if (!this.isRunning) return;
620
+ this.update();
621
+ this.animationId = requestAnimationFrame(() => this.animate());
622
+ }
623
+ start() {
624
+ if (this.isRunning) return;
625
+ this.isRunning = true;
626
+ this.animate();
627
+ }
628
+ stop() {
629
+ this.isRunning = false;
630
+ if (this.animationId) {
631
+ cancelAnimationFrame(this.animationId);
632
+ this.animationId = null;
633
+ }
634
+ }
635
+ setupEventListeners() {
636
+ window.addEventListener("resize", this.handleResize);
637
+ this.canvas.addEventListener("mousemove", this.handleMouseMove);
638
+ this.canvas.addEventListener("mouseleave", this.handleMouseLeave);
639
+ this.canvas.addEventListener("mousedown", this.handleMouseDown);
640
+ window.addEventListener("mouseup", this.handleMouseUp);
641
+ }
642
+ removeEventListeners() {
643
+ window.removeEventListener("resize", this.handleResize);
644
+ this.canvas.removeEventListener("mousemove", this.handleMouseMove);
645
+ this.canvas.removeEventListener("mouseleave", this.handleMouseLeave);
646
+ this.canvas.removeEventListener("mousedown", this.handleMouseDown);
647
+ window.removeEventListener("mouseup", this.handleMouseUp);
648
+ }
649
+ handleResize() {
650
+ this.resize();
651
+ }
652
+ handleMouseMove(e) {
653
+ const rect = this.canvas.getBoundingClientRect();
654
+ this.mouse.x = (e.clientX - rect.left) * this.DPR;
655
+ this.mouse.y = (e.clientY - rect.top) * this.DPR;
656
+ }
657
+ handleMouseLeave() {
658
+ this.mouse.x = this.mouse.y = 0;
659
+ }
660
+ handleMouseDown() {
661
+ this.mouse.down = true;
662
+ }
663
+ handleMouseUp() {
664
+ this.mouse.down = false;
665
+ }
666
+ // 설정 업데이트
667
+ updateConfig(newConfig) {
668
+ this._debouncedUpdateConfig(newConfig);
669
+ }
670
+ _updateConfigInternal(newConfig) {
671
+ this.config = { ...this.config, ...newConfig };
672
+ if (newConfig.text) {
673
+ this.buildTargets();
674
+ }
675
+ }
676
+ // 파괴 및 정리
677
+ destroy() {
678
+ this.stop();
679
+ this.removeEventListeners();
680
+ if (this.intersectionObserver) {
681
+ this.intersectionObserver.disconnect();
682
+ this.intersectionObserver = null;
683
+ }
684
+ if (this.canvas && this.canvas.parentNode) {
685
+ this.canvas.parentNode.removeChild(this.canvas);
686
+ }
687
+ }
688
+ }
689
+ function create_fragment(ctx) {
690
+ let div;
691
+ return {
692
+ c() {
693
+ div = element("div");
694
+ attr(
695
+ div,
696
+ "class",
697
+ /*className*/
698
+ ctx[0]
699
+ );
700
+ attr(
701
+ div,
702
+ "style",
703
+ /*style*/
704
+ ctx[1]
705
+ );
706
+ },
707
+ m(target, anchor) {
708
+ insert(target, div, anchor);
709
+ ctx[21](div);
710
+ },
711
+ p(ctx2, [dirty]) {
712
+ if (dirty & /*className*/
713
+ 1) {
714
+ attr(
715
+ div,
716
+ "class",
717
+ /*className*/
718
+ ctx2[0]
719
+ );
720
+ }
721
+ if (dirty & /*style*/
722
+ 2) {
723
+ attr(
724
+ div,
725
+ "style",
726
+ /*style*/
727
+ ctx2[1]
728
+ );
729
+ }
730
+ },
731
+ i: noop,
732
+ o: noop,
733
+ d(detaching) {
734
+ if (detaching) {
735
+ detach(div);
736
+ }
737
+ ctx[21](null);
738
+ }
739
+ };
740
+ }
741
+ function instance_1($$self, $$props, $$invalidate) {
742
+ let { text = "mason effect" } = $$props;
743
+ let { densityStep = 2 } = $$props;
744
+ let { maxParticles = 3200 } = $$props;
745
+ let { pointSize = 0.5 } = $$props;
746
+ let { ease = 0.05 } = $$props;
747
+ let { repelRadius = 150 } = $$props;
748
+ let { repelStrength = 1 } = $$props;
749
+ let { particleColor = "#fff" } = $$props;
750
+ let { fontFamily = "Inter, system-ui, Arial" } = $$props;
751
+ let { fontSize = null } = $$props;
752
+ let { width = null } = $$props;
753
+ let { height = null } = $$props;
754
+ let { devicePixelRatio = null } = $$props;
755
+ let { className = "" } = $$props;
756
+ let { style = {} } = $$props;
757
+ const dispatch = createEventDispatcher();
758
+ let container;
759
+ let instance = null;
760
+ onMount(() => {
761
+ if (!container) return;
762
+ const options = {
763
+ text,
764
+ densityStep,
765
+ maxParticles,
766
+ pointSize,
767
+ ease,
768
+ repelRadius,
769
+ repelStrength,
770
+ particleColor,
771
+ fontFamily,
772
+ fontSize,
773
+ width,
774
+ height,
775
+ devicePixelRatio,
776
+ onReady: (inst) => {
777
+ dispatch("ready", inst);
778
+ },
779
+ onUpdate: (inst) => {
780
+ dispatch("update", inst);
781
+ }
782
+ };
783
+ $$invalidate(20, instance = new MasonEffect(container, options));
784
+ });
785
+ onDestroy(() => {
786
+ if (instance) {
787
+ instance.destroy();
788
+ $$invalidate(20, instance = null);
789
+ }
790
+ });
791
+ function morph(textOrOptions) {
792
+ if (instance) {
793
+ instance.morph(textOrOptions);
794
+ }
795
+ }
796
+ function scatter() {
797
+ if (instance) {
798
+ instance.scatter();
799
+ }
800
+ }
801
+ function updateConfig(config) {
802
+ if (instance) {
803
+ instance.updateConfig(config);
804
+ }
805
+ }
806
+ function destroy() {
807
+ if (instance) {
808
+ instance.destroy();
809
+ $$invalidate(20, instance = null);
810
+ }
811
+ }
812
+ function div_binding($$value) {
813
+ binding_callbacks[$$value ? "unshift" : "push"](() => {
814
+ container = $$value;
815
+ $$invalidate(2, container);
816
+ });
817
+ }
818
+ $$self.$$set = ($$props2) => {
819
+ if ("text" in $$props2) $$invalidate(3, text = $$props2.text);
820
+ if ("densityStep" in $$props2) $$invalidate(4, densityStep = $$props2.densityStep);
821
+ if ("maxParticles" in $$props2) $$invalidate(5, maxParticles = $$props2.maxParticles);
822
+ if ("pointSize" in $$props2) $$invalidate(6, pointSize = $$props2.pointSize);
823
+ if ("ease" in $$props2) $$invalidate(7, ease = $$props2.ease);
824
+ if ("repelRadius" in $$props2) $$invalidate(8, repelRadius = $$props2.repelRadius);
825
+ if ("repelStrength" in $$props2) $$invalidate(9, repelStrength = $$props2.repelStrength);
826
+ if ("particleColor" in $$props2) $$invalidate(10, particleColor = $$props2.particleColor);
827
+ if ("fontFamily" in $$props2) $$invalidate(11, fontFamily = $$props2.fontFamily);
828
+ if ("fontSize" in $$props2) $$invalidate(12, fontSize = $$props2.fontSize);
829
+ if ("width" in $$props2) $$invalidate(13, width = $$props2.width);
830
+ if ("height" in $$props2) $$invalidate(14, height = $$props2.height);
831
+ if ("devicePixelRatio" in $$props2) $$invalidate(15, devicePixelRatio = $$props2.devicePixelRatio);
832
+ if ("className" in $$props2) $$invalidate(0, className = $$props2.className);
833
+ if ("style" in $$props2) $$invalidate(1, style = $$props2.style);
834
+ };
835
+ $$self.$$.update = () => {
836
+ if ($$self.$$.dirty & /*instance, text, densityStep, maxParticles, pointSize, ease, repelRadius, repelStrength, particleColor, fontFamily, fontSize, width, height, devicePixelRatio*/
837
+ 1114104) {
838
+ {
839
+ if (instance) {
840
+ instance.updateConfig({
841
+ text,
842
+ densityStep,
843
+ maxParticles,
844
+ pointSize,
845
+ ease,
846
+ repelRadius,
847
+ repelStrength,
848
+ particleColor,
849
+ fontFamily,
850
+ fontSize,
851
+ width,
852
+ height,
853
+ devicePixelRatio
854
+ });
855
+ }
856
+ }
857
+ }
858
+ };
859
+ return [
860
+ className,
861
+ style,
862
+ container,
863
+ text,
864
+ densityStep,
865
+ maxParticles,
866
+ pointSize,
867
+ ease,
868
+ repelRadius,
869
+ repelStrength,
870
+ particleColor,
871
+ fontFamily,
872
+ fontSize,
873
+ width,
874
+ height,
875
+ devicePixelRatio,
876
+ morph,
877
+ scatter,
878
+ updateConfig,
879
+ destroy,
880
+ instance,
881
+ div_binding
882
+ ];
883
+ }
884
+ class MasonEffect_1 extends SvelteComponent {
885
+ constructor(options) {
886
+ super();
887
+ init(this, options, instance_1, create_fragment, safe_not_equal, {
888
+ text: 3,
889
+ densityStep: 4,
890
+ maxParticles: 5,
891
+ pointSize: 6,
892
+ ease: 7,
893
+ repelRadius: 8,
894
+ repelStrength: 9,
895
+ particleColor: 10,
896
+ fontFamily: 11,
897
+ fontSize: 12,
898
+ width: 13,
899
+ height: 14,
900
+ devicePixelRatio: 15,
901
+ className: 0,
902
+ style: 1,
903
+ morph: 16,
904
+ scatter: 17,
905
+ updateConfig: 18,
906
+ destroy: 19
907
+ });
908
+ }
909
+ get morph() {
910
+ return this.$$.ctx[16];
911
+ }
912
+ get scatter() {
913
+ return this.$$.ctx[17];
914
+ }
915
+ get updateConfig() {
916
+ return this.$$.ctx[18];
917
+ }
918
+ get destroy() {
919
+ return this.$$.ctx[19];
920
+ }
921
+ }
922
+ export {
923
+ MasonEffect_1 as default
924
+ };