easter-egg-quest 1.0.14 → 1.0.16
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.
|
@@ -462,6 +462,12 @@ class InputTracker {
|
|
|
462
462
|
this._phases = [];
|
|
463
463
|
this._currentPhaseType = "still";
|
|
464
464
|
this._currentPhaseStart = Date.now();
|
|
465
|
+
this._snapshot.isMoving = false;
|
|
466
|
+
this._snapshot.velocity = 0;
|
|
467
|
+
if (this._stillTimer) {
|
|
468
|
+
clearTimeout(this._stillTimer);
|
|
469
|
+
this._stillTimer = null;
|
|
470
|
+
}
|
|
465
471
|
}
|
|
466
472
|
resetCounts() {
|
|
467
473
|
this._snapshot.totalClicks = 0;
|
|
@@ -567,6 +573,10 @@ class HiddenEntry {
|
|
|
567
573
|
this._hintVisibleStart = 0;
|
|
568
574
|
this._hintCheckInterval = null;
|
|
569
575
|
this._hintVisibilityHandler = null;
|
|
576
|
+
this._domObserver = null;
|
|
577
|
+
this._intersectionObserver = null;
|
|
578
|
+
this._mutationDebounce = null;
|
|
579
|
+
this._navigationHandler = null;
|
|
570
580
|
this.config = config;
|
|
571
581
|
this.script = script;
|
|
572
582
|
this.onFound = onFound;
|
|
@@ -600,6 +610,7 @@ class HiddenEntry {
|
|
|
600
610
|
this.targetElement.style.removeProperty("animation");
|
|
601
611
|
this.targetElement.classList.remove("eeq-entry-target");
|
|
602
612
|
}
|
|
613
|
+
this._stopWatchdog();
|
|
603
614
|
(_a2 = this._injectedElement) == null ? void 0 : _a2.remove();
|
|
604
615
|
this._injectedElement = null;
|
|
605
616
|
(_b2 = this.hintContainer) == null ? void 0 : _b2.remove();
|
|
@@ -620,8 +631,8 @@ class HiddenEntry {
|
|
|
620
631
|
).filter((el) => {
|
|
621
632
|
var _a2;
|
|
622
633
|
const text = ((_a2 = el.textContent) == null ? void 0 : _a2.trim()) ?? "";
|
|
623
|
-
|
|
624
|
-
return
|
|
634
|
+
if (text.length < 2 || el.closest("[data-eeq]")) return false;
|
|
635
|
+
return isElementVisible(el);
|
|
625
636
|
});
|
|
626
637
|
const sorted = candidates.sort((a, b) => {
|
|
627
638
|
const aNav = a.closest("nav") !== null || a.closest("header") !== null ? 1 : 0;
|
|
@@ -708,10 +719,113 @@ class HiddenEntry {
|
|
|
708
719
|
this._injectedElement = span;
|
|
709
720
|
this.targetElement = span;
|
|
710
721
|
this._attachToElement(span);
|
|
722
|
+
this._startWatchdog();
|
|
711
723
|
return;
|
|
712
724
|
}
|
|
713
725
|
this._createFallbackEntry();
|
|
714
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Watch for the injected trigger element being removed from the DOM
|
|
729
|
+
* (SPA re-render, route change, virtual scroll) or scrolled out of view.
|
|
730
|
+
*
|
|
731
|
+
* Uses IntersectionObserver (zero layout cost) for visibility,
|
|
732
|
+
* a MutationObserver on a stable ancestor (survives Angular/React re-renders),
|
|
733
|
+
* and navigation event listeners for SPA route changes.
|
|
734
|
+
*/
|
|
735
|
+
_startWatchdog() {
|
|
736
|
+
this._stopWatchdog();
|
|
737
|
+
if (!this._injectedElement) return;
|
|
738
|
+
this._intersectionObserver = new IntersectionObserver(
|
|
739
|
+
(entries) => {
|
|
740
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
741
|
+
const entry = entries[0];
|
|
742
|
+
if (entry && !entry.isIntersecting) {
|
|
743
|
+
this._reinject();
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
{ threshold: 0 }
|
|
747
|
+
);
|
|
748
|
+
this._intersectionObserver.observe(this._injectedElement);
|
|
749
|
+
const observeTarget = this._findStableAncestor(this._injectedElement);
|
|
750
|
+
this._domObserver = new MutationObserver(() => {
|
|
751
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
752
|
+
if (this._mutationDebounce) clearTimeout(this._mutationDebounce);
|
|
753
|
+
this._mutationDebounce = setTimeout(() => {
|
|
754
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
755
|
+
if (!document.body.contains(this._injectedElement)) {
|
|
756
|
+
this._reinject();
|
|
757
|
+
}
|
|
758
|
+
}, 300);
|
|
759
|
+
});
|
|
760
|
+
this._domObserver.observe(observeTarget, { childList: true, subtree: true });
|
|
761
|
+
this._navigationHandler = () => {
|
|
762
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
763
|
+
setTimeout(() => {
|
|
764
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
765
|
+
if (!document.body.contains(this._injectedElement)) {
|
|
766
|
+
this._reinject();
|
|
767
|
+
}
|
|
768
|
+
}, 500);
|
|
769
|
+
};
|
|
770
|
+
window.addEventListener("popstate", this._navigationHandler);
|
|
771
|
+
window.addEventListener("hashchange", this._navigationHandler);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Walk up the DOM to find a stable ancestor that won't be replaced
|
|
775
|
+
* by SPA framework routing (Angular <router-outlet>, React root, etc.).
|
|
776
|
+
*/
|
|
777
|
+
_findStableAncestor(el) {
|
|
778
|
+
let node = el.parentElement;
|
|
779
|
+
while (node && node !== document.body) {
|
|
780
|
+
const tag = node.tagName.toLowerCase();
|
|
781
|
+
if (tag === "main" || tag === "body" || tag.includes("app-") || // Angular app-root, app-component, etc.
|
|
782
|
+
tag.includes("-root") || // custom element roots
|
|
783
|
+
node.querySelector("router-outlet") !== null || node.id === "app" || node.id === "root" || node.id === "__next") {
|
|
784
|
+
return node;
|
|
785
|
+
}
|
|
786
|
+
node = node.parentElement;
|
|
787
|
+
}
|
|
788
|
+
return document.body;
|
|
789
|
+
}
|
|
790
|
+
_stopWatchdog() {
|
|
791
|
+
if (this._domObserver) {
|
|
792
|
+
this._domObserver.disconnect();
|
|
793
|
+
this._domObserver = null;
|
|
794
|
+
}
|
|
795
|
+
if (this._intersectionObserver) {
|
|
796
|
+
this._intersectionObserver.disconnect();
|
|
797
|
+
this._intersectionObserver = null;
|
|
798
|
+
}
|
|
799
|
+
if (this._mutationDebounce) {
|
|
800
|
+
clearTimeout(this._mutationDebounce);
|
|
801
|
+
this._mutationDebounce = null;
|
|
802
|
+
}
|
|
803
|
+
if (this._navigationHandler) {
|
|
804
|
+
window.removeEventListener("popstate", this._navigationHandler);
|
|
805
|
+
window.removeEventListener("hashchange", this._navigationHandler);
|
|
806
|
+
this._navigationHandler = null;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/** Remove old trigger (if still in DOM) and re-inject into a new element. */
|
|
810
|
+
_reinject() {
|
|
811
|
+
this._stopWatchdog();
|
|
812
|
+
if (this.clickHandler && this.targetElement) {
|
|
813
|
+
this.targetElement.removeEventListener("click", this.clickHandler);
|
|
814
|
+
}
|
|
815
|
+
if (this._injectedElement && document.body.contains(this._injectedElement)) {
|
|
816
|
+
this._injectedElement.remove();
|
|
817
|
+
}
|
|
818
|
+
this._injectedElement = null;
|
|
819
|
+
this.targetElement = null;
|
|
820
|
+
setTimeout(() => {
|
|
821
|
+
if (this._destroyed) return;
|
|
822
|
+
const eligible = findEligibleEntryElements(
|
|
823
|
+
this.config.hiddenEntry.selector,
|
|
824
|
+
this.config.hiddenEntry.excludeSelectors
|
|
825
|
+
);
|
|
826
|
+
this._injectIntoExisting(eligible);
|
|
827
|
+
}, 1200);
|
|
828
|
+
}
|
|
715
829
|
/**
|
|
716
830
|
* Try to insert the trigger span between words inside a text node.
|
|
717
831
|
* Picks a random word boundary in the element's first direct text node.
|
|
@@ -1059,7 +1173,7 @@ class NarrativeRenderer {
|
|
|
1059
1173
|
width: "100%",
|
|
1060
1174
|
height: "100%",
|
|
1061
1175
|
pointerEvents: "none",
|
|
1062
|
-
zIndex: "
|
|
1176
|
+
zIndex: "999993"
|
|
1063
1177
|
});
|
|
1064
1178
|
document.body.appendChild(this.host);
|
|
1065
1179
|
this.shadow = this.host.attachShadow({ mode: "closed" });
|
|
@@ -1889,6 +2003,13 @@ class ThreeRenderer {
|
|
|
1889
2003
|
}
|
|
1890
2004
|
}
|
|
1891
2005
|
}
|
|
2006
|
+
/** Mark all ambient particles for rapid fade-out (rhythm lost). */
|
|
2007
|
+
fadeOutAmbientParticles() {
|
|
2008
|
+
for (const p of this._ambientParticles) {
|
|
2009
|
+
p.fadeIn = false;
|
|
2010
|
+
if (p.life > 0.3) p.life = 0.3;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
1892
2013
|
/** Start the finale composition. */
|
|
1893
2014
|
startFinale() {
|
|
1894
2015
|
this._finaleActive = true;
|
|
@@ -2209,17 +2330,17 @@ class ThreeRenderer {
|
|
|
2209
2330
|
this.eggBodies.push(body3);
|
|
2210
2331
|
}
|
|
2211
2332
|
// ── Mini egg helpers for particles ─────────────────────────────────────
|
|
2212
|
-
/** Create a tiny egg-shaped geometry
|
|
2333
|
+
/** Create a tiny egg-shaped geometry — rounder, less elongated. */
|
|
2213
2334
|
_createMiniEggGeo(T, radius) {
|
|
2214
|
-
const geo = new T.SphereGeometry(radius,
|
|
2335
|
+
const geo = new T.SphereGeometry(radius, 12, 12);
|
|
2215
2336
|
const pos = geo.attributes.position;
|
|
2216
2337
|
for (let i = 0; i < pos.count; i++) {
|
|
2217
2338
|
let y = pos.getY(i);
|
|
2218
2339
|
const x = pos.getX(i);
|
|
2219
2340
|
const z = pos.getZ(i);
|
|
2220
|
-
const topSquash = y > 0 ? 0.
|
|
2221
|
-
y = y * 1.
|
|
2222
|
-
const narrowing = 1 - Math.abs(y) * 0.
|
|
2341
|
+
const topSquash = y > 0 ? 0.95 : 1;
|
|
2342
|
+
y = y * 1.08 * topSquash;
|
|
2343
|
+
const narrowing = 1 - Math.abs(y) * 0.03;
|
|
2223
2344
|
pos.setX(i, x * narrowing);
|
|
2224
2345
|
pos.setY(i, y);
|
|
2225
2346
|
pos.setZ(i, z * narrowing);
|
|
@@ -2235,8 +2356,8 @@ class ThreeRenderer {
|
|
|
2235
2356
|
c.height = 64;
|
|
2236
2357
|
const ctx = c.getContext("2d");
|
|
2237
2358
|
const hue = Math.random() * 360;
|
|
2238
|
-
const sat =
|
|
2239
|
-
const light =
|
|
2359
|
+
const sat = 60 + Math.random() * 25;
|
|
2360
|
+
const light = 68 + Math.random() * 14;
|
|
2240
2361
|
ctx.fillStyle = `hsl(${hue}, ${sat}%, ${light}%)`;
|
|
2241
2362
|
ctx.fillRect(0, 0, 64, 64);
|
|
2242
2363
|
const accentHue = (hue + 80 + Math.random() * 100) % 360;
|
|
@@ -2307,7 +2428,7 @@ class ThreeRenderer {
|
|
|
2307
2428
|
return new T.MeshBasicMaterial({
|
|
2308
2429
|
map: tex,
|
|
2309
2430
|
transparent: true,
|
|
2310
|
-
opacity
|
|
2431
|
+
opacity: opacity || 0.85
|
|
2311
2432
|
});
|
|
2312
2433
|
}
|
|
2313
2434
|
_createEggGeometry(T) {
|
|
@@ -3037,12 +3158,12 @@ class ThreeRenderer {
|
|
|
3037
3158
|
p.mesh.rotation.z += dt * 0.5;
|
|
3038
3159
|
if (p.fadeIn && p.life > 0.7) {
|
|
3039
3160
|
p.mesh.material.opacity = Math.min(
|
|
3040
|
-
0.
|
|
3041
|
-
p.mesh.material.opacity + dt *
|
|
3161
|
+
0.9,
|
|
3162
|
+
p.mesh.material.opacity + dt * 1
|
|
3042
3163
|
);
|
|
3043
|
-
if (p.mesh.material.opacity >= 0.
|
|
3164
|
+
if (p.mesh.material.opacity >= 0.85) p.fadeIn = false;
|
|
3044
3165
|
} else {
|
|
3045
|
-
p.mesh.material.opacity = Math.max(0, p.life * 0.
|
|
3166
|
+
p.mesh.material.opacity = Math.max(0, p.life * 0.85);
|
|
3046
3167
|
}
|
|
3047
3168
|
if (p.life <= 0) {
|
|
3048
3169
|
this.scene.remove(p.mesh);
|
|
@@ -4511,6 +4632,7 @@ class RhythmStage {
|
|
|
4511
4632
|
if (isGood) {
|
|
4512
4633
|
this._goodCycles++;
|
|
4513
4634
|
} else {
|
|
4635
|
+
this._goodCycles = Math.max(0, this._goodCycles - 1);
|
|
4514
4636
|
this._showReaction();
|
|
4515
4637
|
}
|
|
4516
4638
|
this._lastPhaseCount += 2;
|
|
@@ -4670,6 +4792,7 @@ class PageBreather {
|
|
|
4670
4792
|
this._startTime = 0;
|
|
4671
4793
|
this._isUserMoving = false;
|
|
4672
4794
|
this._inSync = false;
|
|
4795
|
+
this._accuracy = 0;
|
|
4673
4796
|
this.CYCLE_MS = 6e3;
|
|
4674
4797
|
this._tick = () => {
|
|
4675
4798
|
if (!this._active || !this._overlay) return;
|
|
@@ -4679,7 +4802,7 @@ class PageBreather {
|
|
|
4679
4802
|
const phase = elapsed % this.CYCLE_MS / this.CYCLE_MS;
|
|
4680
4803
|
const wave = (Math.sin(phase * Math.PI * 2 - Math.PI / 2) + 1) * 0.5;
|
|
4681
4804
|
const shouldMove = phase < 0.5;
|
|
4682
|
-
this._inSync = shouldMove === this._isUserMoving;
|
|
4805
|
+
this._inSync = this._accuracy > 0 && shouldMove === this._isUserMoving;
|
|
4683
4806
|
const baseOpacity = wave * 0.85;
|
|
4684
4807
|
const syncBonus = this._inSync ? 0.15 : 0;
|
|
4685
4808
|
const opacity = this._intensity * (baseOpacity + (1 - wave) * 0.05 + syncBonus);
|
|
@@ -4710,6 +4833,7 @@ class PageBreather {
|
|
|
4710
4833
|
update(accuracy, isMoving) {
|
|
4711
4834
|
if (!this._active) return;
|
|
4712
4835
|
this._isUserMoving = isMoving;
|
|
4836
|
+
this._accuracy = accuracy;
|
|
4713
4837
|
const raw = Math.max(0, Math.min(1, (accuracy - 0.05) / 0.5));
|
|
4714
4838
|
this._targetIntensity = 0.5 + raw * 0.5;
|
|
4715
4839
|
}
|
|
@@ -4727,6 +4851,7 @@ class PageBreather {
|
|
|
4727
4851
|
this._intensity = 0;
|
|
4728
4852
|
this._targetIntensity = 0;
|
|
4729
4853
|
this._inSync = false;
|
|
4854
|
+
this._accuracy = 0;
|
|
4730
4855
|
}
|
|
4731
4856
|
destroy() {
|
|
4732
4857
|
this.stop();
|
|
@@ -4825,11 +4950,13 @@ class GameController {
|
|
|
4825
4950
|
(_a3 = this.eggRenderer) == null ? void 0 : _a3.addTrail(data.x, data.y, data.velocity);
|
|
4826
4951
|
});
|
|
4827
4952
|
this.bus.on("rhythm:breathe", (data) => {
|
|
4828
|
-
var _a3, _b3
|
|
4953
|
+
var _a3, _b3;
|
|
4829
4954
|
(_a3 = this.eggRenderer) == null ? void 0 : _a3.setBreathIntensity(data.accuracy);
|
|
4830
4955
|
(_b3 = this.pageBreather) == null ? void 0 : _b3.update(data.accuracy, data.isMoving);
|
|
4831
|
-
if (
|
|
4956
|
+
if (data.accuracy > 0 && this.threeRenderer) {
|
|
4832
4957
|
this.threeRenderer.showProgressParticles(0.6 + data.accuracy * 0.4);
|
|
4958
|
+
} else if (this.threeRenderer) {
|
|
4959
|
+
this.threeRenderer.fadeOutAmbientParticles();
|
|
4833
4960
|
}
|
|
4834
4961
|
});
|
|
4835
4962
|
this.overlay = new OverlayManager(this.config);
|
|
@@ -5147,6 +5274,7 @@ class GameController {
|
|
|
5147
5274
|
}
|
|
5148
5275
|
const successLines = eggIndex === 0 ? this.script.stage1Success : eggIndex === 1 ? this.script.stage2Success : this.script.stage3Success;
|
|
5149
5276
|
await ((_c = this.narrative) == null ? void 0 : _c.showSequence(successLines, 4500));
|
|
5277
|
+
await this._wait(3e3);
|
|
5150
5278
|
(_d = this.narrative) == null ? void 0 : _d.clear();
|
|
5151
5279
|
await this._wait(800);
|
|
5152
5280
|
if (this.threeRenderer) {
|