easter-egg-quest 1.0.18 → 1.0.19
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/dist/easter-egg-quest.es.js +119 -52
- package/dist/easter-egg-quest.umd.js +1 -1
- package/dist/types/input/InputTracker.d.ts +9 -2
- package/dist/types/rendering/NarrativeRenderer.d.ts +3 -1
- package/dist/types/rendering/PageBreather.d.ts +1 -0
- package/dist/types/stages/RhythmStage.d.ts +1 -0
- package/package.json +1 -1
|
@@ -57,33 +57,24 @@ const DEFAULT_SCRIPT = {
|
|
|
57
57
|
stage2Success: [
|
|
58
58
|
"movement was the answer",
|
|
59
59
|
"the second egg appears",
|
|
60
|
-
"
|
|
61
|
-
"not stillness this time",
|
|
62
|
-
"but life in motion"
|
|
60
|
+
"hold it to collect"
|
|
63
61
|
],
|
|
64
62
|
// ── Stage 3: Rhythm ───────────────────────────────────────────────────
|
|
65
63
|
stage3Intro: [
|
|
66
|
-
"two truths that shouldn’t exist together",
|
|
67
64
|
"the final riddle:",
|
|
68
|
-
"
|
|
65
|
+
"Every living thing knows it. You’ve been doing it your whole life without thinking."
|
|
69
66
|
],
|
|
70
67
|
stage3Reactions: [
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"you’ve been doing it since birth",
|
|
76
|
-
"listen to yourself",
|
|
77
|
-
"closer...",
|
|
78
|
-
"can you feel it?",
|
|
79
|
-
"the body remembers",
|
|
68
|
+
"move for a few seconds",
|
|
69
|
+
"then hold still for a moment",
|
|
70
|
+
"don’t rush it",
|
|
71
|
+
"alternate move and pause",
|
|
80
72
|
"in",
|
|
81
73
|
"out"
|
|
82
74
|
],
|
|
83
75
|
stage3Success: [
|
|
84
76
|
"rhythm was the answer",
|
|
85
|
-
"you found the third egg"
|
|
86
|
-
"nice work"
|
|
77
|
+
"you found the third egg"
|
|
87
78
|
],
|
|
88
79
|
// ── Finale ────────────────────────────────────────────────────────────
|
|
89
80
|
finale: [
|
|
@@ -331,8 +322,10 @@ class InputTracker {
|
|
|
331
322
|
this._prevX = 0;
|
|
332
323
|
this._prevY = 0;
|
|
333
324
|
this._prevMoveTs = 0;
|
|
334
|
-
this.
|
|
335
|
-
this.
|
|
325
|
+
this.DEFAULT_STILL_THRESHOLD_MS = 400;
|
|
326
|
+
this._stillThresholdMs = this.DEFAULT_STILL_THRESHOLD_MS;
|
|
327
|
+
this.DEFAULT_MIN_PHASE_MS = 300;
|
|
328
|
+
this._minPhaseMs = this.DEFAULT_MIN_PHASE_MS;
|
|
336
329
|
this._stillTimer = null;
|
|
337
330
|
this._handlers = [];
|
|
338
331
|
this._active = false;
|
|
@@ -471,6 +464,18 @@ class InputTracker {
|
|
|
471
464
|
this._snapshot.totalDistance = 0;
|
|
472
465
|
this._snapshot.maxVelocity = 0;
|
|
473
466
|
}
|
|
467
|
+
configurePhaseDetection(opts) {
|
|
468
|
+
if (typeof opts.stillThresholdMs === "number") {
|
|
469
|
+
this._stillThresholdMs = Math.max(250, opts.stillThresholdMs);
|
|
470
|
+
}
|
|
471
|
+
if (typeof opts.minPhaseMs === "number") {
|
|
472
|
+
this._minPhaseMs = Math.max(150, opts.minPhaseMs);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
resetPhaseDetection() {
|
|
476
|
+
this._stillThresholdMs = this.DEFAULT_STILL_THRESHOLD_MS;
|
|
477
|
+
this._minPhaseMs = this.DEFAULT_MIN_PHASE_MS;
|
|
478
|
+
}
|
|
474
479
|
// ─── Movement / Stillness transition logic ─────────────────────────────
|
|
475
480
|
_registerMove(now) {
|
|
476
481
|
this._snapshot.lastMoveTime = now;
|
|
@@ -483,7 +488,7 @@ class InputTracker {
|
|
|
483
488
|
if (this._stillTimer) clearTimeout(this._stillTimer);
|
|
484
489
|
this._stillTimer = setTimeout(() => {
|
|
485
490
|
this._becomeStill();
|
|
486
|
-
}, this.
|
|
491
|
+
}, this._stillThresholdMs);
|
|
487
492
|
}
|
|
488
493
|
_becomeStill() {
|
|
489
494
|
if (!this._snapshot.isMoving) return;
|
|
@@ -497,7 +502,7 @@ class InputTracker {
|
|
|
497
502
|
}
|
|
498
503
|
_completePhase(type, endTime) {
|
|
499
504
|
const duration = endTime - this._currentPhaseStart;
|
|
500
|
-
if (duration >= this.
|
|
505
|
+
if (duration >= this._minPhaseMs) {
|
|
501
506
|
this._phases.push({
|
|
502
507
|
type,
|
|
503
508
|
startTime: this._currentPhaseStart,
|
|
@@ -1312,6 +1317,7 @@ class NarrativeRenderer {
|
|
|
1312
1317
|
this.container = null;
|
|
1313
1318
|
this.currentLine = null;
|
|
1314
1319
|
this.clearTimer = null;
|
|
1320
|
+
this.awaitingConfirm = false;
|
|
1315
1321
|
this.config = config;
|
|
1316
1322
|
}
|
|
1317
1323
|
mount() {
|
|
@@ -1335,14 +1341,22 @@ class NarrativeRenderer {
|
|
|
1335
1341
|
this.container.className = "eeq-narrative";
|
|
1336
1342
|
this.shadow.appendChild(this.container);
|
|
1337
1343
|
}
|
|
1344
|
+
_removeCurrentLine(immediate = false) {
|
|
1345
|
+
if (!this.currentLine) return;
|
|
1346
|
+
const prev = this.currentLine;
|
|
1347
|
+
this.currentLine = null;
|
|
1348
|
+
if (immediate) {
|
|
1349
|
+
prev.remove();
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
prev.classList.add("eeq-narrative-exit");
|
|
1353
|
+
setTimeout(() => prev.remove(), 750);
|
|
1354
|
+
}
|
|
1338
1355
|
/** Show a single narrative line. Fades in, replaces any previous line. */
|
|
1339
1356
|
showLine(text, className = "eeq-line") {
|
|
1340
1357
|
if (!this.container) return;
|
|
1341
|
-
if (this.
|
|
1342
|
-
|
|
1343
|
-
prev.classList.add("eeq-narrative-exit");
|
|
1344
|
-
setTimeout(() => prev.remove(), 750);
|
|
1345
|
-
}
|
|
1358
|
+
if (this.awaitingConfirm) return;
|
|
1359
|
+
this._removeCurrentLine(true);
|
|
1346
1360
|
if (this.clearTimer) {
|
|
1347
1361
|
clearTimeout(this.clearTimer);
|
|
1348
1362
|
this.clearTimer = null;
|
|
@@ -1371,11 +1385,8 @@ class NarrativeRenderer {
|
|
|
1371
1385
|
resolve();
|
|
1372
1386
|
return;
|
|
1373
1387
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
prev.classList.add("eeq-narrative-exit");
|
|
1377
|
-
setTimeout(() => prev.remove(), 750);
|
|
1378
|
-
}
|
|
1388
|
+
this.awaitingConfirm = true;
|
|
1389
|
+
this._removeCurrentLine(true);
|
|
1379
1390
|
if (this.clearTimer) {
|
|
1380
1391
|
clearTimeout(this.clearTimer);
|
|
1381
1392
|
this.clearTimer = null;
|
|
@@ -1387,7 +1398,10 @@ class NarrativeRenderer {
|
|
|
1387
1398
|
const btn = document.createElement("button");
|
|
1388
1399
|
btn.className = "eeq-confirm-btn";
|
|
1389
1400
|
btn.textContent = "OK";
|
|
1390
|
-
btn.addEventListener("click", () =>
|
|
1401
|
+
btn.addEventListener("click", () => {
|
|
1402
|
+
this.awaitingConfirm = false;
|
|
1403
|
+
resolve();
|
|
1404
|
+
}, { once: true });
|
|
1391
1405
|
line.appendChild(btn);
|
|
1392
1406
|
requestAnimationFrame(() => {
|
|
1393
1407
|
requestAnimationFrame(() => {
|
|
@@ -1409,22 +1423,19 @@ class NarrativeRenderer {
|
|
|
1409
1423
|
this.showLine(text, "eeq-line eeq-celebration");
|
|
1410
1424
|
}
|
|
1411
1425
|
/** Clear current narrative text. */
|
|
1412
|
-
clear() {
|
|
1426
|
+
clear(force = false) {
|
|
1427
|
+
if (this.awaitingConfirm && !force) return;
|
|
1413
1428
|
if (this.clearTimer) {
|
|
1414
1429
|
clearTimeout(this.clearTimer);
|
|
1415
1430
|
this.clearTimer = null;
|
|
1416
1431
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const ref = this.currentLine;
|
|
1420
|
-
setTimeout(() => ref.remove(), 750);
|
|
1421
|
-
this.currentLine = null;
|
|
1422
|
-
}
|
|
1432
|
+
this._removeCurrentLine(force);
|
|
1433
|
+
this.awaitingConfirm = false;
|
|
1423
1434
|
}
|
|
1424
1435
|
/** Remove all DOM. */
|
|
1425
1436
|
destroy() {
|
|
1426
1437
|
var _a2;
|
|
1427
|
-
this.clear();
|
|
1438
|
+
this.clear(true);
|
|
1428
1439
|
(_a2 = this.host) == null ? void 0 : _a2.remove();
|
|
1429
1440
|
this.host = null;
|
|
1430
1441
|
this.shadow = null;
|
|
@@ -4569,6 +4580,8 @@ class StillnessStage {
|
|
|
4569
4580
|
this._attempts = 1;
|
|
4570
4581
|
this.input.resetPhases();
|
|
4571
4582
|
this.input.resetCounts();
|
|
4583
|
+
this.bus.emit("narrative:clear");
|
|
4584
|
+
await this._wait(850);
|
|
4572
4585
|
this._introPlayed = false;
|
|
4573
4586
|
const introLines = this.script.stage1Intro;
|
|
4574
4587
|
for (let i = 0; i < introLines.length; i++) {
|
|
@@ -4663,6 +4676,8 @@ class MotionStage {
|
|
|
4663
4676
|
this._attempts = 1;
|
|
4664
4677
|
this.input.resetPhases();
|
|
4665
4678
|
this.input.resetCounts();
|
|
4679
|
+
this.bus.emit("narrative:clear");
|
|
4680
|
+
await this._wait(850);
|
|
4666
4681
|
this._introPlayed = false;
|
|
4667
4682
|
const introLines = this.script.stage2Intro;
|
|
4668
4683
|
for (let i = 0; i < introLines.length; i++) {
|
|
@@ -4762,6 +4777,7 @@ class RhythmStage {
|
|
|
4762
4777
|
this._totalCycles = 0;
|
|
4763
4778
|
this._lastPhaseCount = 0;
|
|
4764
4779
|
this._bestAccuracy = 0;
|
|
4780
|
+
this._lastGuideTime = 0;
|
|
4765
4781
|
this.IDEAL_MOVE_MIN = 1800;
|
|
4766
4782
|
this.IDEAL_MOVE_MAX = 5e3;
|
|
4767
4783
|
this.IDEAL_PAUSE_MIN = 1200;
|
|
@@ -4776,8 +4792,11 @@ class RhythmStage {
|
|
|
4776
4792
|
this._attempts = 1;
|
|
4777
4793
|
this._goodCycles = 0;
|
|
4778
4794
|
this._totalCycles = 0;
|
|
4795
|
+
this.input.configurePhaseDetection({ stillThresholdMs: 900, minPhaseMs: 500 });
|
|
4779
4796
|
this.input.resetPhases();
|
|
4780
4797
|
this.input.resetCounts();
|
|
4798
|
+
this.bus.emit("narrative:clear");
|
|
4799
|
+
await this._wait(850);
|
|
4781
4800
|
this._introPlayed = false;
|
|
4782
4801
|
const introLines = this.script.stage3Intro;
|
|
4783
4802
|
for (let i = 0; i < introLines.length; i++) {
|
|
@@ -4793,6 +4812,8 @@ class RhythmStage {
|
|
|
4793
4812
|
this.input.resetPhases();
|
|
4794
4813
|
this._lastPhaseCount = this.input.phases.length;
|
|
4795
4814
|
this._introPlayed = true;
|
|
4815
|
+
this.bus.emit("narrative:show", "move for a few seconds, then hold still");
|
|
4816
|
+
this._lastGuideTime = Date.now();
|
|
4796
4817
|
}
|
|
4797
4818
|
update(_dt) {
|
|
4798
4819
|
if (this._status === "complete" || !this._introPlayed) return this._status;
|
|
@@ -4819,7 +4840,17 @@ class RhythmStage {
|
|
|
4819
4840
|
if (accuracy > this._bestAccuracy) this._bestAccuracy = accuracy;
|
|
4820
4841
|
const progress = Math.min(1, this._goodCycles / requiredCycles);
|
|
4821
4842
|
this.bus.emit("stage:progress", progress);
|
|
4822
|
-
this.bus.emit("rhythm:breathe", {
|
|
4843
|
+
this.bus.emit("rhythm:breathe", {
|
|
4844
|
+
accuracy,
|
|
4845
|
+
isMoving: this.input.snapshot.isMoving,
|
|
4846
|
+
progress,
|
|
4847
|
+
moveDuration: this.input.moveDuration,
|
|
4848
|
+
stillDuration: this.input.stillDuration
|
|
4849
|
+
});
|
|
4850
|
+
if (accuracy === 0 && Date.now() - this._lastGuideTime > 12e3) {
|
|
4851
|
+
this._lastGuideTime = Date.now();
|
|
4852
|
+
this.bus.emit("narrative:show", "try this: move 2-4s, then stay still 1-3s");
|
|
4853
|
+
}
|
|
4823
4854
|
if (this._goodCycles >= requiredCycles) {
|
|
4824
4855
|
this._status = "complete";
|
|
4825
4856
|
this.bus.emit("stage:progress", 1);
|
|
@@ -4829,6 +4860,7 @@ class RhythmStage {
|
|
|
4829
4860
|
return this._status;
|
|
4830
4861
|
}
|
|
4831
4862
|
cleanup() {
|
|
4863
|
+
this.input.resetPhaseDetection();
|
|
4832
4864
|
}
|
|
4833
4865
|
getResult() {
|
|
4834
4866
|
return {
|
|
@@ -4848,7 +4880,7 @@ class RhythmStage {
|
|
|
4848
4880
|
}
|
|
4849
4881
|
_showReaction() {
|
|
4850
4882
|
const now = Date.now();
|
|
4851
|
-
if (now - this._lastNarrativeTime >
|
|
4883
|
+
if (now - this._lastNarrativeTime > 12e3) {
|
|
4852
4884
|
this._lastNarrativeTime = now;
|
|
4853
4885
|
const reactions = this.script.stage3Reactions;
|
|
4854
4886
|
const line = reactions[this._narrativeIndex % reactions.length];
|
|
@@ -4964,6 +4996,7 @@ class PageBreather {
|
|
|
4964
4996
|
this._targetIntensity = 0;
|
|
4965
4997
|
this._rafId = 0;
|
|
4966
4998
|
this._overlay = null;
|
|
4999
|
+
this._label = null;
|
|
4967
5000
|
this._startTime = 0;
|
|
4968
5001
|
this._isUserMoving = false;
|
|
4969
5002
|
this._inSync = false;
|
|
@@ -4979,9 +5012,16 @@ class PageBreather {
|
|
|
4979
5012
|
const shouldMove = phase < 0.5;
|
|
4980
5013
|
this._inSync = this._accuracy > 0 && shouldMove === this._isUserMoving;
|
|
4981
5014
|
const baseOpacity = wave * 0.85;
|
|
4982
|
-
const syncBonus = this._inSync ? 0.
|
|
4983
|
-
const opacity = this._intensity * (baseOpacity + (1 - wave) * 0.
|
|
4984
|
-
this._overlay.style.opacity = String(Math.min(
|
|
5015
|
+
const syncBonus = this._inSync ? 0.22 : 0;
|
|
5016
|
+
const opacity = this._intensity * (0.18 + baseOpacity + (1 - wave) * 0.12 + syncBonus);
|
|
5017
|
+
this._overlay.style.opacity = String(Math.min(0.9, opacity));
|
|
5018
|
+
this._overlay.style.transform = `scale(${1 + wave * 0.015})`;
|
|
5019
|
+
if (this._label) {
|
|
5020
|
+
this._label.textContent = shouldMove ? "Move" : "Hold still";
|
|
5021
|
+
this._label.style.opacity = shouldMove === this._isUserMoving ? "0.98" : "0.82";
|
|
5022
|
+
this._label.style.boxShadow = this._inSync ? "0 0 28px rgba(212,165,80,0.4)" : "0 0 20px rgba(255,255,255,0.08)";
|
|
5023
|
+
this._label.style.borderColor = this._inSync ? "rgba(212,165,80,0.55)" : "rgba(255,255,255,0.12)";
|
|
5024
|
+
}
|
|
4985
5025
|
};
|
|
4986
5026
|
}
|
|
4987
5027
|
start() {
|
|
@@ -4998,31 +5038,56 @@ class PageBreather {
|
|
|
4998
5038
|
"pointer-events:none",
|
|
4999
5039
|
"z-index:999980",
|
|
5000
5040
|
"opacity:0",
|
|
5001
|
-
"background:radial-gradient(ellipse at center,
|
|
5041
|
+
"background:radial-gradient(ellipse at center, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0.02) 24%, rgba(8,12,22,0.18) 46%, rgba(4,6,12,0.82) 100%)",
|
|
5002
5042
|
"transition:opacity 0.3s ease"
|
|
5003
5043
|
].join(";");
|
|
5004
5044
|
document.body.appendChild(el);
|
|
5005
5045
|
this._overlay = el;
|
|
5046
|
+
const label = document.createElement("div");
|
|
5047
|
+
label.style.cssText = [
|
|
5048
|
+
"position:fixed",
|
|
5049
|
+
"left:50%",
|
|
5050
|
+
"top:18%",
|
|
5051
|
+
"transform:translateX(-50%)",
|
|
5052
|
+
"pointer-events:none",
|
|
5053
|
+
"z-index:999981",
|
|
5054
|
+
"padding:8px 14px",
|
|
5055
|
+
"border-radius:999px",
|
|
5056
|
+
"font-family:Georgia, Times New Roman, serif",
|
|
5057
|
+
"font-size:12px",
|
|
5058
|
+
"letter-spacing:0.12em",
|
|
5059
|
+
"text-transform:uppercase",
|
|
5060
|
+
"color:rgba(255,248,232,0.95)",
|
|
5061
|
+
"background:rgba(8,10,18,0.36)",
|
|
5062
|
+
"border:1px solid rgba(255,255,255,0.12)",
|
|
5063
|
+
"box-shadow:0 0 20px rgba(255,255,255,0.08)",
|
|
5064
|
+
"opacity:0.78"
|
|
5065
|
+
].join(";");
|
|
5066
|
+
label.textContent = "Move";
|
|
5067
|
+
document.body.appendChild(label);
|
|
5068
|
+
this._label = label;
|
|
5006
5069
|
this._tick();
|
|
5007
5070
|
}
|
|
5008
5071
|
update(accuracy, isMoving) {
|
|
5009
5072
|
if (!this._active) return;
|
|
5010
5073
|
this._isUserMoving = isMoving;
|
|
5011
5074
|
this._accuracy = accuracy;
|
|
5012
|
-
const raw = Math.max(0, Math.min(1, (accuracy - 0.
|
|
5013
|
-
this._targetIntensity = 0.
|
|
5075
|
+
const raw = Math.max(0, Math.min(1, (accuracy - 0.02) / 0.45));
|
|
5076
|
+
this._targetIntensity = 0.68 + raw * 0.32;
|
|
5014
5077
|
}
|
|
5015
5078
|
/** Whether the user is currently in sync with the breathing rhythm. */
|
|
5016
5079
|
isInSync() {
|
|
5017
5080
|
return this._inSync;
|
|
5018
5081
|
}
|
|
5019
5082
|
stop() {
|
|
5020
|
-
var _a2;
|
|
5083
|
+
var _a2, _b2;
|
|
5021
5084
|
if (!this._active) return;
|
|
5022
5085
|
this._active = false;
|
|
5023
5086
|
cancelAnimationFrame(this._rafId);
|
|
5024
5087
|
(_a2 = this._overlay) == null ? void 0 : _a2.remove();
|
|
5025
5088
|
this._overlay = null;
|
|
5089
|
+
(_b2 = this._label) == null ? void 0 : _b2.remove();
|
|
5090
|
+
this._label = null;
|
|
5026
5091
|
this._intensity = 0;
|
|
5027
5092
|
this._targetIntensity = 0;
|
|
5028
5093
|
this._inSync = false;
|
|
@@ -5116,7 +5181,7 @@ class GameController {
|
|
|
5116
5181
|
});
|
|
5117
5182
|
this.bus.on("narrative:clear", () => {
|
|
5118
5183
|
var _a3;
|
|
5119
|
-
return (_a3 = this.narrative) == null ? void 0 : _a3.clear();
|
|
5184
|
+
return (_a3 = this.narrative) == null ? void 0 : _a3.clear(true);
|
|
5120
5185
|
});
|
|
5121
5186
|
this.bus.on("egg:reveal", (index) => this._handleEggReveal(index));
|
|
5122
5187
|
this.bus.on("stage:progress", (p) => this._handleStageProgress(p));
|
|
@@ -5128,8 +5193,10 @@ class GameController {
|
|
|
5128
5193
|
var _a3, _b3;
|
|
5129
5194
|
(_a3 = this.eggRenderer) == null ? void 0 : _a3.setBreathIntensity(data.accuracy);
|
|
5130
5195
|
(_b3 = this.pageBreather) == null ? void 0 : _b3.update(data.accuracy, data.isMoving);
|
|
5131
|
-
|
|
5132
|
-
|
|
5196
|
+
const rhythmSignal = Math.max(data.accuracy, data.progress * 0.75);
|
|
5197
|
+
const hasLiveCycleSignal = data.moveDuration > 1200 || data.stillDuration > 900;
|
|
5198
|
+
if ((rhythmSignal > 0.08 || hasLiveCycleSignal) && this.threeRenderer) {
|
|
5199
|
+
this.threeRenderer.showProgressParticles(0.45 + rhythmSignal * 0.55);
|
|
5133
5200
|
} else if (this.threeRenderer) {
|
|
5134
5201
|
this.threeRenderer.fadeOutAmbientParticles();
|
|
5135
5202
|
}
|
|
@@ -5493,7 +5560,7 @@ class GameController {
|
|
|
5493
5560
|
(_d = this.narrative) == null ? void 0 : _d.showCelebration(congrats[eggIndex] ?? "egg collected");
|
|
5494
5561
|
await this._wait(3500);
|
|
5495
5562
|
(_e = this.narrative) == null ? void 0 : _e.clear();
|
|
5496
|
-
await this._wait(
|
|
5563
|
+
await this._wait(900);
|
|
5497
5564
|
if (eggIndex < 2) {
|
|
5498
5565
|
if (eggIndex === 0) {
|
|
5499
5566
|
this.fsm.transitionTo("stage2-intro");
|