easter-egg-quest 1.0.22 → 1.0.23

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.
@@ -2178,6 +2178,41 @@ class ThreeRenderer {
2178
2178
  }
2179
2179
  }
2180
2180
  }
2181
+ burstRhythmParticles(progress, cycles) {
2182
+ if (!this.THREE || !this.scene) return;
2183
+ const T = this.THREE;
2184
+ const burstSize = Math.min(14, 6 + Math.floor(progress * 6) + Math.min(3, Math.floor(cycles)));
2185
+ for (let i = 0; i < burstSize; i++) {
2186
+ const angle = Math.PI * 2 * i / burstSize + (Math.random() - 0.5) * 0.35;
2187
+ const radius = 0.45 + Math.random() * 2.15;
2188
+ const x = Math.cos(angle) * radius;
2189
+ const y = -2.95 - Math.random() * 0.75;
2190
+ const size = 0.036 + Math.random() * 0.038;
2191
+ const geo = this._createMiniEggGeo(T, size);
2192
+ const mat = this._createMiniEggMat(T, 0.18 + Math.random() * 0.12);
2193
+ const particle = new T.Mesh(geo, mat);
2194
+ particle.position.set(x, y, -1 + Math.random() * 0.8);
2195
+ particle.rotation.set(
2196
+ Math.random() * Math.PI,
2197
+ Math.random() * Math.PI,
2198
+ Math.random() * Math.PI
2199
+ );
2200
+ this.scene.add(particle);
2201
+ this._ambientParticles.push({
2202
+ mesh: particle,
2203
+ life: 1.7 + Math.random() * 0.45,
2204
+ vy: 0.85 + Math.random() * 0.65,
2205
+ fadeIn: true
2206
+ });
2207
+ }
2208
+ while (this._ambientParticles.length > 120) {
2209
+ const old = this._ambientParticles.shift();
2210
+ if (!old) break;
2211
+ this.scene.remove(old.mesh);
2212
+ old.mesh.geometry.dispose();
2213
+ old.mesh.material.dispose();
2214
+ }
2215
+ }
2181
2216
  /** Mark all ambient particles for rapid fade-out (rhythm lost). */
2182
2217
  fadeOutAmbientParticles() {
2183
2218
  for (const p of this._ambientParticles) {
@@ -4500,6 +4535,7 @@ class ScoringEngine {
4500
4535
  }
4501
4536
  const STORAGE_KEY = "eeq_scores";
4502
4537
  const PROGRESS_KEY = "eeq_progress";
4538
+ const COMPLETION_KEY = "eeq_completed";
4503
4539
  class Persistence {
4504
4540
  constructor(enabled) {
4505
4541
  this._enabled = enabled;
@@ -4572,6 +4608,22 @@ class Persistence {
4572
4608
  } catch {
4573
4609
  }
4574
4610
  }
4611
+ markCompleted() {
4612
+ if (!this._enabled) return;
4613
+ try {
4614
+ localStorage.setItem(COMPLETION_KEY, "1");
4615
+ localStorage.removeItem(PROGRESS_KEY);
4616
+ } catch {
4617
+ }
4618
+ }
4619
+ isCompleted() {
4620
+ if (!this._enabled) return false;
4621
+ try {
4622
+ return localStorage.getItem(COMPLETION_KEY) === "1";
4623
+ } catch {
4624
+ return false;
4625
+ }
4626
+ }
4575
4627
  }
4576
4628
  class StillnessStage {
4577
4629
  constructor(config, script, input, bus) {
@@ -4841,6 +4893,10 @@ class RhythmStage {
4841
4893
  if (isGood) {
4842
4894
  this._goodCycles++;
4843
4895
  this._lastGoodCycleAt = Date.now();
4896
+ this.bus.emit("rhythm:cycle", {
4897
+ progress: Math.min(1, this._goodCycles / requiredCycles),
4898
+ cycles: this._goodCycles
4899
+ });
4844
4900
  } else {
4845
4901
  this._goodCycles = Math.max(0, this._goodCycles - 0.5);
4846
4902
  this._showReaction();
@@ -5038,7 +5094,6 @@ class PageBreather {
5038
5094
  this._targetIntensity = 0;
5039
5095
  this._rafId = 0;
5040
5096
  this._overlay = null;
5041
- this._label = null;
5042
5097
  this._startTime = 0;
5043
5098
  this._isUserMoving = false;
5044
5099
  this._inSync = false;
@@ -5059,12 +5114,6 @@ class PageBreather {
5059
5114
  const opacity = this._intensity * (baseOpacity + syncBonus);
5060
5115
  this._overlay.style.opacity = String(Math.min(0.9, opacity));
5061
5116
  this._overlay.style.transform = `scale(${1 + wave * (shouldMove ? 0.01 : 0.02)})`;
5062
- if (this._label) {
5063
- this._label.textContent = shouldMove ? "In" : "Out";
5064
- this._label.style.opacity = this._phaseMatch ? "0.98" : "0.78";
5065
- this._label.style.boxShadow = this._inSync ? "0 0 28px rgba(212,165,80,0.4)" : "0 0 20px rgba(255,255,255,0.08)";
5066
- this._label.style.borderColor = this._inSync ? "rgba(212,165,80,0.55)" : "rgba(255,255,255,0.12)";
5067
- }
5068
5117
  };
5069
5118
  }
5070
5119
  start() {
@@ -5086,29 +5135,6 @@ class PageBreather {
5086
5135
  ].join(";");
5087
5136
  document.body.appendChild(el);
5088
5137
  this._overlay = el;
5089
- const label = document.createElement("div");
5090
- label.style.cssText = [
5091
- "position:fixed",
5092
- "left:50%",
5093
- "top:18%",
5094
- "transform:translateX(-50%)",
5095
- "pointer-events:none",
5096
- "z-index:999981",
5097
- "padding:8px 14px",
5098
- "border-radius:999px",
5099
- "font-family:Georgia, Times New Roman, serif",
5100
- "font-size:12px",
5101
- "letter-spacing:0.12em",
5102
- "text-transform:uppercase",
5103
- "color:rgba(255,248,232,0.95)",
5104
- "background:rgba(8,10,18,0.36)",
5105
- "border:1px solid rgba(255,255,255,0.12)",
5106
- "box-shadow:0 0 20px rgba(255,255,255,0.08)",
5107
- "opacity:0.78"
5108
- ].join(";");
5109
- label.textContent = "In";
5110
- document.body.appendChild(label);
5111
- this._label = label;
5112
5138
  this._tick();
5113
5139
  }
5114
5140
  update(accuracy, isMoving, targetPhase, phaseMatch, signal) {
@@ -5126,14 +5152,12 @@ class PageBreather {
5126
5152
  return this._inSync;
5127
5153
  }
5128
5154
  stop() {
5129
- var _a2, _b2;
5155
+ var _a2;
5130
5156
  if (!this._active) return;
5131
5157
  this._active = false;
5132
5158
  cancelAnimationFrame(this._rafId);
5133
5159
  (_a2 = this._overlay) == null ? void 0 : _a2.remove();
5134
5160
  this._overlay = null;
5135
- (_b2 = this._label) == null ? void 0 : _b2.remove();
5136
- this._label = null;
5137
5161
  this._intensity = 0;
5138
5162
  this._targetIntensity = 0;
5139
5163
  this._inSync = false;
@@ -5218,6 +5242,10 @@ class GameController {
5218
5242
  // ─── Lifecycle ─────────────────────────────────────────────────────────
5219
5243
  async init() {
5220
5244
  var _a2, _b2;
5245
+ if (this.persistence.isCompleted()) {
5246
+ this._destroyed = true;
5247
+ return;
5248
+ }
5221
5249
  this.fsm.onTransition((from, to) => this._onTransition(from, to));
5222
5250
  this.bus.on("narrative:show", (text) => {
5223
5251
  var _a3;
@@ -5238,6 +5266,10 @@ class GameController {
5238
5266
  var _a3;
5239
5267
  (_a3 = this.eggRenderer) == null ? void 0 : _a3.addTrail(data.x, data.y, data.velocity);
5240
5268
  });
5269
+ this.bus.on("rhythm:cycle", (data) => {
5270
+ if (!this.threeRenderer) return;
5271
+ this.threeRenderer.burstRhythmParticles(data.progress, data.cycles);
5272
+ });
5241
5273
  this.bus.on("rhythm:breathe", (data) => {
5242
5274
  var _a3, _b3;
5243
5275
  (_a3 = this.eggRenderer) == null ? void 0 : _a3.setBreathIntensity(data.accuracy);
@@ -5248,9 +5280,7 @@ class GameController {
5248
5280
  data.phaseMatch,
5249
5281
  Math.max(data.liveConfidence * 0.55, data.cycleSignal)
5250
5282
  );
5251
- if (data.cycleSignal > 0.12 && this.threeRenderer) {
5252
- this.threeRenderer.showProgressParticles(0.35 + Math.max(data.progress, data.cycleSignal) * 0.65, "rhythm");
5253
- } else if (this.threeRenderer) {
5283
+ if (data.cycleSignal <= 0.12 && this.threeRenderer) {
5254
5284
  this.threeRenderer.fadeOutAmbientParticles();
5255
5285
  }
5256
5286
  });
@@ -5432,6 +5462,7 @@ class GameController {
5432
5462
  // ─── Entry ─────────────────────────────────────────────────────────────
5433
5463
  async _startEntry() {
5434
5464
  var _a2;
5465
+ if (this.persistence.isCompleted()) return;
5435
5466
  try {
5436
5467
  if (localStorage.getItem("eeq_optout") === "1") return;
5437
5468
  } catch {
@@ -5681,7 +5712,9 @@ class GameController {
5681
5712
  this.results.show(
5682
5713
  score,
5683
5714
  () => {
5715
+ this.persistence.markCompleted();
5684
5716
  this.destroy();
5717
+ window.location.reload();
5685
5718
  }
5686
5719
  );
5687
5720
  (_c = (_b2 = this.config.callbacks).onComplete) == null ? void 0 : _c.call(_b2, score);