levita-js 0.2.0 → 0.2.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/dist/index.cjs CHANGED
@@ -184,9 +184,12 @@ const cleanupLayers = (layers) => {
184
184
  */
185
185
  var MotionSensor = class MotionSensor {
186
186
  onMove;
187
+ onFirstEvent;
187
188
  axis;
188
189
  active = false;
189
190
  permitted = false;
191
+ receivedEvent = false;
192
+ warmup = false;
190
193
  minAngle;
191
194
  maxAngle;
192
195
  smoothing;
@@ -200,13 +203,15 @@ var MotionSensor = class MotionSensor {
200
203
  * @param minAngle - Minimum device angle mapped to -1 (default: -45)
201
204
  * @param maxAngle - Maximum device angle mapped to 1 (default: 45)
202
205
  * @param smoothing - Exponential moving average factor 0-1 (default: 0.15, lower = smoother)
206
+ * @param onFirstEvent - Called once when the first valid deviceorientation event is received
203
207
  */
204
- constructor(onMove, axis, minAngle = -45, maxAngle = 45, smoothing = .15) {
208
+ constructor(onMove, axis, minAngle = -45, maxAngle = 45, smoothing = .15, onFirstEvent = null) {
205
209
  this.onMove = onMove;
206
210
  this.axis = axis;
207
211
  this.minAngle = minAngle;
208
212
  this.maxAngle = maxAngle;
209
213
  this.smoothing = smoothing;
214
+ this.onFirstEvent = onFirstEvent;
210
215
  }
211
216
  /** Check if the DeviceOrientationEvent API is available in this environment. */
212
217
  static isSupported = () => {
@@ -245,6 +250,8 @@ var MotionSensor = class MotionSensor {
245
250
  start = () => {
246
251
  if (this.active || !this.permitted) return;
247
252
  this.active = true;
253
+ this.receivedEvent = false;
254
+ this.warmup = false;
248
255
  this.baseBeta = null;
249
256
  this.baseGamma = null;
250
257
  window.addEventListener("deviceorientation", this.handleOrientation);
@@ -268,8 +275,17 @@ var MotionSensor = class MotionSensor {
268
275
  */
269
276
  handleOrientation = (e) => {
270
277
  const evt = e;
278
+ if (evt.beta === null && evt.gamma === null) return;
279
+ if (!this.warmup) {
280
+ this.warmup = true;
281
+ return;
282
+ }
271
283
  const beta = evt.beta ?? 0;
272
284
  const gamma = evt.gamma ?? 0;
285
+ if (!this.receivedEvent) {
286
+ this.receivedEvent = true;
287
+ this.onFirstEvent?.();
288
+ }
273
289
  if (this.baseBeta === null) {
274
290
  this.baseBeta = beta;
275
291
  this.baseGamma = gamma;
@@ -406,6 +422,7 @@ var Levita = class {
406
422
  listeners = /* @__PURE__ */ new Map();
407
423
  destroyed = false;
408
424
  gyroscopeRequested = false;
425
+ gyroscopeEvent = null;
409
426
  rafId = null;
410
427
  /**
411
428
  * @param el - The DOM element to apply the tilt effect to
@@ -424,23 +441,31 @@ var Levita = class {
424
441
  if (this.options.shadow) this.shadowEffect = new ShadowEffect(this.el);
425
442
  this.pointerSensor = new PointerSensor(this.el, (values) => this.handleSensorInput(values), () => this.handleEnter(), () => this.handleLeave(), this.options.axis, this.options.eventsEl);
426
443
  if (this.options.gyroscope !== false && MotionSensor.isSupported()) {
427
- this.motionSensor = new MotionSensor((values) => this.handleSensorInput(values), this.options.axis);
428
- if (this.options.gyroscope === "auto") this.el.addEventListener("click", this.handleFirstTouch, { once: true });
444
+ this.motionSensor = new MotionSensor((values) => this.handleSensorInput(values), this.options.axis, void 0, void 0, void 0, () => this.handleMotionReady());
445
+ if (this.options.gyroscope === "auto") {
446
+ this.gyroscopeEvent = MotionSensor.needsPermission() ? "click" : "pointerup";
447
+ this.el.addEventListener(this.gyroscopeEvent, this.handleFirstTouch, { once: true });
448
+ }
429
449
  }
430
450
  if (!this.options.disabled) this.enable();
431
451
  }
432
452
  /**
433
- * On first touch (iOS auto mode), request accelerometer permission
434
- * and switch from pointer to motion sensor if granted.
453
+ * On first interaction, request accelerometer permission and start the
454
+ * motion sensor. The pointer sensor keeps running until the first valid
455
+ * deviceorientation event is received (see handleMotionReady).
435
456
  */
436
457
  handleFirstTouch = async () => {
437
458
  if (this.destroyed || this.gyroscopeRequested || !this.motionSensor) return;
438
459
  this.gyroscopeRequested = true;
439
- if (await this.motionSensor.requestPermission()) {
440
- this.pointerSensor.stop();
441
- this.setTransition(false);
442
- this.motionSensor.start();
443
- }
460
+ if (await this.motionSensor.requestPermission()) this.motionSensor.start();
461
+ };
462
+ /**
463
+ * Called when the motion sensor receives its first valid event.
464
+ * At this point it's safe to hand off from pointer to motion.
465
+ */
466
+ handleMotionReady = () => {
467
+ this.pointerSensor.stop();
468
+ this.setTransition(false);
444
469
  };
445
470
  /**
446
471
  * Update options at runtime without destroying/recreating the instance.
@@ -481,11 +506,7 @@ var Levita = class {
481
506
  requestPermission = async () => {
482
507
  if (!this.motionSensor) return false;
483
508
  const granted = await this.motionSensor.requestPermission();
484
- if (granted) {
485
- this.pointerSensor.stop();
486
- this.setTransition(false);
487
- this.motionSensor.start();
488
- }
509
+ if (granted) this.motionSensor.start();
489
510
  return granted;
490
511
  };
491
512
  /**
@@ -504,7 +525,7 @@ var Levita = class {
504
525
  this.el.classList.remove("levita");
505
526
  this.removeBaseProperties();
506
527
  this.listeners.clear();
507
- this.el.removeEventListener("click", this.handleFirstTouch);
528
+ if (this.gyroscopeEvent) this.el.removeEventListener(this.gyroscopeEvent, this.handleFirstTouch);
508
529
  };
509
530
  /**
510
531
  * Register an event listener.
package/dist/index.d.cts CHANGED
@@ -91,6 +91,7 @@ declare class Levita {
91
91
  private listeners;
92
92
  private destroyed;
93
93
  private gyroscopeRequested;
94
+ private gyroscopeEvent;
94
95
  private rafId;
95
96
  /**
96
97
  * @param el - The DOM element to apply the tilt effect to
@@ -98,10 +99,16 @@ declare class Levita {
98
99
  */
99
100
  constructor(el: HTMLElement, options?: Partial<LevitaOptions>);
100
101
  /**
101
- * On first touch (iOS auto mode), request accelerometer permission
102
- * and switch from pointer to motion sensor if granted.
102
+ * On first interaction, request accelerometer permission and start the
103
+ * motion sensor. The pointer sensor keeps running until the first valid
104
+ * deviceorientation event is received (see handleMotionReady).
103
105
  */
104
106
  private handleFirstTouch;
107
+ /**
108
+ * Called when the motion sensor receives its first valid event.
109
+ * At this point it's safe to hand off from pointer to motion.
110
+ */
111
+ private handleMotionReady;
105
112
  /**
106
113
  * Update options at runtime without destroying/recreating the instance.
107
114
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;;;;;KAMA,gBAAA,GAAmB,IAAA,CAC9B,aAAA;AAAA,UAIgB,aAAA;;EAEhB,GAAA;EAbwB;EAexB,WAAA;EAT2B;EAW3B,KAAA;EAX8B;EAa9B,KAAA;EARgB;EAUhB,MAAA;;EAEA,OAAA;EAYW;EAVX,IAAA,EAAM,IAAA;EAce;EAZrB,KAAA;EAdA;EAgBA,KAAA;EAZA;EAcA,QAAA;EAVA;EAYA,MAAA;EARA;EAUA,SAAA,EAAW,aAAA;EARX;EAUA,QAAA;EANA;EAQA,QAAA,EAAU,WAAA;AAAA;AAAA,UAGM,UAAA;EALhB;EAOA,CAAA;EALU;EAOV,CAAA;EAPqB;EASrB,QAAA;EAN0B;EAQ1B,QAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,IAAA,EAAM,UAAA;EACN,KAAA;EACA,KAAA;AAAA;AAAA,KAGW,aAAA,OAAoB,IAAA,EAAM,CAAA;;;AA5DtC;AAAA,cCGa,WAAA,kBAA6B,aAAA;;;;ADF1C;cCuBa,YAAA,GAAgB,MAAA,EAAQ,OAAA,CAAQ,aAAA,MAAiB,OAAA,CAAQ,aAAA;AAAA,cAWzD,eAAA,EAAiB,aAAA;;;ADnC9B;;;;;AACA;;;;;AAMA;;;;;AAKA;AAZA,cEgCa,MAAA;EAAA,QACJ,EAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,kBAAA;EAAA,QACA,KAAA;EFnBR;;;;cEyBY,EAAA,EAAI,WAAA,EAAa,OAAA,GAAS,OAAA,CAAQ,aAAA;EFjB9C;;;;EAAA,QE8DQ,gBAAA;EFtDR;;;;AAGD;;;;EEuEC,MAAA,GAAU,OAAA,EAAS,OAAA,CAAQ,gBAAA;EFnE3B;EE+EA,MAAA;EF3EA;EEkFA,OAAA;EFlFQ;AAGT;;;;;EE4FC,iBAAA,QAA8B,OAAA;EF1F9B;;;;EEyGA,OAAA;EFrGwB;;;;;;EE6HxB,EAAA,mBAAsB,cAAA,EACrB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EF/HF;;;;ACzDvC;;ECsMC,GAAA,mBAAuB,cAAA,EACtB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDxMC;EAAA,QC8MjC,IAAA;EDzLI;;;;;;;;;EAAA,QC2MJ,iBAAA;EAAA,QA+BA,WAAA;EAAA,QAMA,WAAA;EDhPqD;EAAA,QC6PrD,aAAA;ED7P0E;EAAA,QCkQ1E,cAAA;EDvPI;EAAA,QCkQJ,mBAAA;;UAOA,oBAAA;AAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;;;;;KAMA,gBAAA,GAAmB,IAAA,CAC9B,aAAA;AAAA,UAIgB,aAAA;;EAEhB,GAAA;EAbwB;EAexB,WAAA;EAT2B;EAW3B,KAAA;EAX8B;EAa9B,KAAA;EARgB;EAUhB,MAAA;;EAEA,OAAA;EAYW;EAVX,IAAA,EAAM,IAAA;EAce;EAZrB,KAAA;EAdA;EAgBA,KAAA;EAZA;EAcA,QAAA;EAVA;EAYA,MAAA;EARA;EAUA,SAAA,EAAW,aAAA;EARX;EAUA,QAAA;EANA;EAQA,QAAA,EAAU,WAAA;AAAA;AAAA,UAGM,UAAA;EALhB;EAOA,CAAA;EALU;EAOV,CAAA;EAPqB;EASrB,QAAA;EAN0B;EAQ1B,QAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,IAAA,EAAM,UAAA;EACN,KAAA;EACA,KAAA;AAAA;AAAA,KAGW,aAAA,OAAoB,IAAA,EAAM,CAAA;;;AA5DtC;AAAA,cCGa,WAAA,kBAA6B,aAAA;;;;ADF1C;cCuBa,YAAA,GAAgB,MAAA,EAAQ,OAAA,CAAQ,aAAA,MAAiB,OAAA,CAAQ,aAAA;AAAA,cAWzD,eAAA,EAAiB,aAAA;;;ADnC9B;;;;;AACA;;;;;AAMA;;;;;AAKA;AAZA,cEgCa,MAAA;EAAA,QACJ,EAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,kBAAA;EAAA,QACA,cAAA;EAAA,QACA,KAAA;EFlBR;;;;cEwBY,EAAA,EAAI,WAAA,EAAa,OAAA,GAAS,OAAA,CAAQ,aAAA;EFhB9C;;;;;EAAA,QEqEQ,gBAAA;EF/Da;;AAGtB;;EAHsB,QE6Eb,iBAAA;EF1EkB;;;;;;;AAW3B;EE4EC,MAAA,GAAU,OAAA,EAAS,OAAA,CAAQ,gBAAA;;EAY3B,MAAA;EFvFA;EE8FA,OAAA;EF7FA;;;;AAID;;EEsGC,iBAAA,QAA8B,OAAA;EFtGQ;;;;EEmHtC,OAAA;EFnHsC;;;;ACzDvC;;ECsMC,EAAA,mBAAsB,cAAA,EACrB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDxMC;;AAqB1C;;;;ECiMC,GAAA,mBAAuB,cAAA,EACtB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDnM6B;EAAA,QCyM7D,IAAA;EDzM4D;;;;;;;;;EAAA,QC2N5D,iBAAA;EAAA,QA+BA,WAAA;EAAA,QAMA,WAAA;EDrPqB;EAAA,QCkQrB,aAAA;;UAKA,cAAA;;UAWA,mBAAA;EArRU;EAAA,QA4RV,oBAAA;AAAA"}
package/dist/index.d.mts CHANGED
@@ -91,6 +91,7 @@ declare class Levita {
91
91
  private listeners;
92
92
  private destroyed;
93
93
  private gyroscopeRequested;
94
+ private gyroscopeEvent;
94
95
  private rafId;
95
96
  /**
96
97
  * @param el - The DOM element to apply the tilt effect to
@@ -98,10 +99,16 @@ declare class Levita {
98
99
  */
99
100
  constructor(el: HTMLElement, options?: Partial<LevitaOptions>);
100
101
  /**
101
- * On first touch (iOS auto mode), request accelerometer permission
102
- * and switch from pointer to motion sensor if granted.
102
+ * On first interaction, request accelerometer permission and start the
103
+ * motion sensor. The pointer sensor keeps running until the first valid
104
+ * deviceorientation event is received (see handleMotionReady).
103
105
  */
104
106
  private handleFirstTouch;
107
+ /**
108
+ * Called when the motion sensor receives its first valid event.
109
+ * At this point it's safe to hand off from pointer to motion.
110
+ */
111
+ private handleMotionReady;
105
112
  /**
106
113
  * Update options at runtime without destroying/recreating the instance.
107
114
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;;;;;KAMA,gBAAA,GAAmB,IAAA,CAC9B,aAAA;AAAA,UAIgB,aAAA;;EAEhB,GAAA;EAbwB;EAexB,WAAA;EAT2B;EAW3B,KAAA;EAX8B;EAa9B,KAAA;EARgB;EAUhB,MAAA;;EAEA,OAAA;EAYW;EAVX,IAAA,EAAM,IAAA;EAce;EAZrB,KAAA;EAdA;EAgBA,KAAA;EAZA;EAcA,QAAA;EAVA;EAYA,MAAA;EARA;EAUA,SAAA,EAAW,aAAA;EARX;EAUA,QAAA;EANA;EAQA,QAAA,EAAU,WAAA;AAAA;AAAA,UAGM,UAAA;EALhB;EAOA,CAAA;EALU;EAOV,CAAA;EAPqB;EASrB,QAAA;EAN0B;EAQ1B,QAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,IAAA,EAAM,UAAA;EACN,KAAA;EACA,KAAA;AAAA;AAAA,KAGW,aAAA,OAAoB,IAAA,EAAM,CAAA;;;AA5DtC;AAAA,cCGa,WAAA,kBAA6B,aAAA;;;;ADF1C;cCuBa,YAAA,GAAgB,MAAA,EAAQ,OAAA,CAAQ,aAAA,MAAiB,OAAA,CAAQ,aAAA;AAAA,cAWzD,eAAA,EAAiB,aAAA;;;ADnC9B;;;;;AACA;;;;;AAMA;;;;;AAKA;AAZA,cEgCa,MAAA;EAAA,QACJ,EAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,kBAAA;EAAA,QACA,KAAA;EFnBR;;;;cEyBY,EAAA,EAAI,WAAA,EAAa,OAAA,GAAS,OAAA,CAAQ,aAAA;EFjB9C;;;;EAAA,QE8DQ,gBAAA;EFtDR;;;;AAGD;;;;EEuEC,MAAA,GAAU,OAAA,EAAS,OAAA,CAAQ,gBAAA;EFnE3B;EE+EA,MAAA;EF3EA;EEkFA,OAAA;EFlFQ;AAGT;;;;;EE4FC,iBAAA,QAA8B,OAAA;EF1F9B;;;;EEyGA,OAAA;EFrGwB;;;;;;EE6HxB,EAAA,mBAAsB,cAAA,EACrB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EF/HF;;;;ACzDvC;;ECsMC,GAAA,mBAAuB,cAAA,EACtB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDxMC;EAAA,QC8MjC,IAAA;EDzLI;;;;;;;;;EAAA,QC2MJ,iBAAA;EAAA,QA+BA,WAAA;EAAA,QAMA,WAAA;EDhPqD;EAAA,QC6PrD,aAAA;ED7P0E;EAAA,QCkQ1E,cAAA;EDvPI;EAAA,QCkQJ,mBAAA;;UAOA,oBAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;;;;;KAMA,gBAAA,GAAmB,IAAA,CAC9B,aAAA;AAAA,UAIgB,aAAA;;EAEhB,GAAA;EAbwB;EAexB,WAAA;EAT2B;EAW3B,KAAA;EAX8B;EAa9B,KAAA;EARgB;EAUhB,MAAA;;EAEA,OAAA;EAYW;EAVX,IAAA,EAAM,IAAA;EAce;EAZrB,KAAA;EAdA;EAgBA,KAAA;EAZA;EAcA,QAAA;EAVA;EAYA,MAAA;EARA;EAUA,SAAA,EAAW,aAAA;EARX;EAUA,QAAA;EANA;EAQA,QAAA,EAAU,WAAA;AAAA;AAAA,UAGM,UAAA;EALhB;EAOA,CAAA;EALU;EAOV,CAAA;EAPqB;EASrB,QAAA;EAN0B;EAQ1B,QAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,IAAA,EAAM,UAAA;EACN,KAAA;EACA,KAAA;AAAA;AAAA,KAGW,aAAA,OAAoB,IAAA,EAAM,CAAA;;;AA5DtC;AAAA,cCGa,WAAA,kBAA6B,aAAA;;;;ADF1C;cCuBa,YAAA,GAAgB,MAAA,EAAQ,OAAA,CAAQ,aAAA,MAAiB,OAAA,CAAQ,aAAA;AAAA,cAWzD,eAAA,EAAiB,aAAA;;;ADnC9B;;;;;AACA;;;;;AAMA;;;;;AAKA;AAZA,cEgCa,MAAA;EAAA,QACJ,EAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,kBAAA;EAAA,QACA,cAAA;EAAA,QACA,KAAA;EFlBR;;;;cEwBY,EAAA,EAAI,WAAA,EAAa,OAAA,GAAS,OAAA,CAAQ,aAAA;EFhB9C;;;;;EAAA,QEqEQ,gBAAA;EF/Da;;AAGtB;;EAHsB,QE6Eb,iBAAA;EF1EkB;;;;;;;AAW3B;EE4EC,MAAA,GAAU,OAAA,EAAS,OAAA,CAAQ,gBAAA;;EAY3B,MAAA;EFvFA;EE8FA,OAAA;EF7FA;;;;AAID;;EEsGC,iBAAA,QAA8B,OAAA;EFtGQ;;;;EEmHtC,OAAA;EFnHsC;;;;ACzDvC;;ECsMC,EAAA,mBAAsB,cAAA,EACrB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDxMC;;AAqB1C;;;;ECiMC,GAAA,mBAAuB,cAAA,EACtB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDnM6B;EAAA,QCyM7D,IAAA;EDzM4D;;;;;;;;;EAAA,QC2N5D,iBAAA;EAAA,QA+BA,WAAA;EAAA,QAMA,WAAA;EDrPqB;EAAA,QCkQrB,aAAA;;UAKA,cAAA;;UAWA,mBAAA;EArRU;EAAA,QA4RV,oBAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -182,9 +182,12 @@ const cleanupLayers = (layers) => {
182
182
  */
183
183
  var MotionSensor = class MotionSensor {
184
184
  onMove;
185
+ onFirstEvent;
185
186
  axis;
186
187
  active = false;
187
188
  permitted = false;
189
+ receivedEvent = false;
190
+ warmup = false;
188
191
  minAngle;
189
192
  maxAngle;
190
193
  smoothing;
@@ -198,13 +201,15 @@ var MotionSensor = class MotionSensor {
198
201
  * @param minAngle - Minimum device angle mapped to -1 (default: -45)
199
202
  * @param maxAngle - Maximum device angle mapped to 1 (default: 45)
200
203
  * @param smoothing - Exponential moving average factor 0-1 (default: 0.15, lower = smoother)
204
+ * @param onFirstEvent - Called once when the first valid deviceorientation event is received
201
205
  */
202
- constructor(onMove, axis, minAngle = -45, maxAngle = 45, smoothing = .15) {
206
+ constructor(onMove, axis, minAngle = -45, maxAngle = 45, smoothing = .15, onFirstEvent = null) {
203
207
  this.onMove = onMove;
204
208
  this.axis = axis;
205
209
  this.minAngle = minAngle;
206
210
  this.maxAngle = maxAngle;
207
211
  this.smoothing = smoothing;
212
+ this.onFirstEvent = onFirstEvent;
208
213
  }
209
214
  /** Check if the DeviceOrientationEvent API is available in this environment. */
210
215
  static isSupported = () => {
@@ -243,6 +248,8 @@ var MotionSensor = class MotionSensor {
243
248
  start = () => {
244
249
  if (this.active || !this.permitted) return;
245
250
  this.active = true;
251
+ this.receivedEvent = false;
252
+ this.warmup = false;
246
253
  this.baseBeta = null;
247
254
  this.baseGamma = null;
248
255
  window.addEventListener("deviceorientation", this.handleOrientation);
@@ -266,8 +273,17 @@ var MotionSensor = class MotionSensor {
266
273
  */
267
274
  handleOrientation = (e) => {
268
275
  const evt = e;
276
+ if (evt.beta === null && evt.gamma === null) return;
277
+ if (!this.warmup) {
278
+ this.warmup = true;
279
+ return;
280
+ }
269
281
  const beta = evt.beta ?? 0;
270
282
  const gamma = evt.gamma ?? 0;
283
+ if (!this.receivedEvent) {
284
+ this.receivedEvent = true;
285
+ this.onFirstEvent?.();
286
+ }
271
287
  if (this.baseBeta === null) {
272
288
  this.baseBeta = beta;
273
289
  this.baseGamma = gamma;
@@ -404,6 +420,7 @@ var Levita = class {
404
420
  listeners = /* @__PURE__ */ new Map();
405
421
  destroyed = false;
406
422
  gyroscopeRequested = false;
423
+ gyroscopeEvent = null;
407
424
  rafId = null;
408
425
  /**
409
426
  * @param el - The DOM element to apply the tilt effect to
@@ -422,23 +439,31 @@ var Levita = class {
422
439
  if (this.options.shadow) this.shadowEffect = new ShadowEffect(this.el);
423
440
  this.pointerSensor = new PointerSensor(this.el, (values) => this.handleSensorInput(values), () => this.handleEnter(), () => this.handleLeave(), this.options.axis, this.options.eventsEl);
424
441
  if (this.options.gyroscope !== false && MotionSensor.isSupported()) {
425
- this.motionSensor = new MotionSensor((values) => this.handleSensorInput(values), this.options.axis);
426
- if (this.options.gyroscope === "auto") this.el.addEventListener("click", this.handleFirstTouch, { once: true });
442
+ this.motionSensor = new MotionSensor((values) => this.handleSensorInput(values), this.options.axis, void 0, void 0, void 0, () => this.handleMotionReady());
443
+ if (this.options.gyroscope === "auto") {
444
+ this.gyroscopeEvent = MotionSensor.needsPermission() ? "click" : "pointerup";
445
+ this.el.addEventListener(this.gyroscopeEvent, this.handleFirstTouch, { once: true });
446
+ }
427
447
  }
428
448
  if (!this.options.disabled) this.enable();
429
449
  }
430
450
  /**
431
- * On first touch (iOS auto mode), request accelerometer permission
432
- * and switch from pointer to motion sensor if granted.
451
+ * On first interaction, request accelerometer permission and start the
452
+ * motion sensor. The pointer sensor keeps running until the first valid
453
+ * deviceorientation event is received (see handleMotionReady).
433
454
  */
434
455
  handleFirstTouch = async () => {
435
456
  if (this.destroyed || this.gyroscopeRequested || !this.motionSensor) return;
436
457
  this.gyroscopeRequested = true;
437
- if (await this.motionSensor.requestPermission()) {
438
- this.pointerSensor.stop();
439
- this.setTransition(false);
440
- this.motionSensor.start();
441
- }
458
+ if (await this.motionSensor.requestPermission()) this.motionSensor.start();
459
+ };
460
+ /**
461
+ * Called when the motion sensor receives its first valid event.
462
+ * At this point it's safe to hand off from pointer to motion.
463
+ */
464
+ handleMotionReady = () => {
465
+ this.pointerSensor.stop();
466
+ this.setTransition(false);
442
467
  };
443
468
  /**
444
469
  * Update options at runtime without destroying/recreating the instance.
@@ -479,11 +504,7 @@ var Levita = class {
479
504
  requestPermission = async () => {
480
505
  if (!this.motionSensor) return false;
481
506
  const granted = await this.motionSensor.requestPermission();
482
- if (granted) {
483
- this.pointerSensor.stop();
484
- this.setTransition(false);
485
- this.motionSensor.start();
486
- }
507
+ if (granted) this.motionSensor.start();
487
508
  return granted;
488
509
  };
489
510
  /**
@@ -502,7 +523,7 @@ var Levita = class {
502
523
  this.el.classList.remove("levita");
503
524
  this.removeBaseProperties();
504
525
  this.listeners.clear();
505
- this.el.removeEventListener("click", this.handleFirstTouch);
526
+ if (this.gyroscopeEvent) this.el.removeEventListener(this.gyroscopeEvent, this.handleFirstTouch);
506
527
  };
507
528
  /**
508
529
  * Register an event listener.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/effects/glare.ts","../src/effects/shadow.ts","../src/layers.ts","../src/sensors/motion.ts","../src/sensors/pointer.ts","../src/levita.ts"],"sourcesContent":["import type { LevitaOptions } from \"./types.js\";\n\n/** All keys of `LevitaOptions`, derived from `DEFAULT_OPTIONS`. */\nexport const OPTION_KEYS: readonly (keyof LevitaOptions)[] = [\n\t\"max\",\n\t\"perspective\",\n\t\"scale\",\n\t\"speed\",\n\t\"easing\",\n\t\"reverse\",\n\t\"axis\",\n\t\"reset\",\n\t\"glare\",\n\t\"maxGlare\",\n\t\"shadow\",\n\t\"gyroscope\",\n\t\"disabled\",\n\t\"eventsEl\",\n] as const;\n\n/**\n * Build a partial `LevitaOptions` object from a source,\n * including only keys that are explicitly defined.\n */\nexport const buildOptions = (source: Partial<LevitaOptions>): Partial<LevitaOptions> => {\n\tconst options: Partial<LevitaOptions> = {};\n\tfor (const key of OPTION_KEYS) {\n\t\tif (source[key] !== undefined) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: generic key assignment\n\t\t\t(options as any)[key] = source[key];\n\t\t}\n\t}\n\treturn options;\n};\n\nexport const DEFAULT_OPTIONS: LevitaOptions = {\n\tmax: 15,\n\tperspective: 1000,\n\tscale: 1.05,\n\tspeed: 200,\n\teasing: \"ease-out\",\n\treverse: false,\n\taxis: null,\n\treset: true,\n\tglare: false,\n\tmaxGlare: 0.5,\n\tshadow: false,\n\tgyroscope: \"auto\",\n\tdisabled: false,\n\teventsEl: null,\n};\n","/**\n * Creates a radial gradient overlay that follows the tilt position,\n * simulating light reflection on the surface.\n *\n * Injects two DOM elements (.levita-glare > .levita-glare-inner) into\n * the target element. Position and opacity are driven by CSS custom\n * properties — no JS runs per animation frame.\n */\nexport class GlareEffect {\n\tprivate container: HTMLElement;\n\tprivate inner: HTMLElement;\n\tprivate maxOpacity: number;\n\n\t/**\n\t * @param el - The element to attach the glare overlay to\n\t * @param maxOpacity - Maximum glare opacity (0-1)\n\t */\n\tconstructor(el: HTMLElement, maxOpacity: number) {\n\t\tthis.maxOpacity = maxOpacity;\n\n\t\tthis.container = document.createElement(\"div\");\n\t\tthis.container.classList.add(\"levita-glare\");\n\n\t\tthis.inner = document.createElement(\"div\");\n\t\tthis.inner.classList.add(\"levita-glare-inner\");\n\n\t\tthis.container.appendChild(this.inner);\n\t\tel.appendChild(this.container);\n\n\t\tif (!el.style.position || el.style.position === \"static\") {\n\t\t\tel.style.position = \"relative\";\n\t\t}\n\t}\n\n\t/**\n\t * Update glare position and intensity based on normalized tilt values.\n\t * Sets CSS custom properties that the stylesheet uses for rendering.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst glareX = ((normalizedX + 1) / 2) * 100;\n\t\tconst glareY = ((normalizedY + 1) / 2) * 100;\n\t\tconst intensity = Math.sqrt(normalizedX ** 2 + normalizedY ** 2) / Math.SQRT2;\n\n\t\tthis.inner.style.setProperty(\"--levita-glare-x\", `${glareX}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-y\", `${glareY}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-opacity\", `${intensity * this.maxOpacity}`);\n\t};\n\n\t/** Remove the glare DOM elements from the parent. */\n\tdestroy = (): void => {\n\t\tthis.container.remove();\n\t};\n}\n","/**\n * Adds a dynamic drop shadow that shifts based on the tilt angle,\n * reinforcing the 3D depth illusion.\n *\n * Uses CSS custom properties (--levita-shadow-x, --levita-shadow-y)\n * combined with `filter: drop-shadow()` — no JS runs per animation frame.\n */\nexport class ShadowEffect {\n\tprivate el: HTMLElement;\n\tprivate maxOffset: number;\n\n\t/**\n\t * @param el - The element to apply the shadow to\n\t * @param maxOffset - Maximum shadow offset in pixels (default: 20)\n\t */\n\tconstructor(el: HTMLElement, maxOffset = 20) {\n\t\tthis.el = el;\n\t\tthis.maxOffset = maxOffset;\n\t\tthis.el.classList.add(\"levita-shadow\");\n\t}\n\n\t/**\n\t * Update shadow offset based on normalized tilt values.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst shadowX = normalizedX * this.maxOffset;\n\t\tconst shadowY = normalizedY * this.maxOffset;\n\n\t\tthis.el.style.setProperty(\"--levita-shadow-x\", `${shadowX}px`);\n\t\tthis.el.style.setProperty(\"--levita-shadow-y\", `${shadowY}px`);\n\t};\n\n\t/** Remove the shadow class and clean up CSS custom properties. */\n\tdestroy = (): void => {\n\t\tthis.el.classList.remove(\"levita-shadow\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-x\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-y\");\n\t};\n}\n","/** Represents a child element with a parallax depth offset. */\nexport interface Layer {\n\t/** The DOM element */\n\tel: HTMLElement;\n\t/** The depth offset value (positive = forward, negative = back) */\n\toffset: number;\n}\n\n/**\n * Scan a container for children with `data-levita-offset` attributes\n * and set the `--levita-offset` CSS custom property on each one.\n *\n * The CSS stylesheet uses this variable with `translateZ()` to position\n * layers at different depths — no JS runs per animation frame.\n *\n * @param container - The parent element to scan for layer children\n * @returns Array of discovered layers with their elements and offsets\n */\nexport const scanLayers = (container: HTMLElement): Layer[] => {\n\tconst elements = container.querySelectorAll<HTMLElement>(\"[data-levita-offset]\");\n\tconst layers: Layer[] = [];\n\n\tfor (const el of elements) {\n\t\tconst raw = el.dataset.levitaOffset;\n\t\tconst offset = Number.parseFloat(raw ?? \"0\");\n\t\tif (!Number.isNaN(offset)) {\n\t\t\tel.style.setProperty(\"--levita-offset\", String(offset));\n\t\t\tlayers.push({ el, offset });\n\t\t}\n\t}\n\n\treturn layers;\n};\n\n/**\n * Remove the `--levita-offset` CSS custom property from all layer elements.\n *\n * @param layers - The layers to clean up\n */\nexport const cleanupLayers = (layers: Layer[]): void => {\n\tfor (const layer of layers) {\n\t\tlayer.el.style.removeProperty(\"--levita-offset\");\n\t}\n};\n","import type { Axis } from \"../types.js\";\nimport type { SensorCallback } from \"./pointer.js\";\n\ninterface DeviceOrientationEvt extends Event {\n\tbeta: number | null;\n\tgamma: number | null;\n}\n\n/**\n * Reads device orientation (accelerometer/gyroscope) and normalizes\n * the tilt angles to a [-1, 1] range.\n *\n * Handles iOS 13+ permission flow via async `requestPermission()`.\n * On Android, permission is granted automatically.\n *\n * Uses exponential moving average for smoothing raw sensor data.\n */\nexport class MotionSensor {\n\tprivate onMove: SensorCallback;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate permitted = false;\n\tprivate minAngle: number;\n\tprivate maxAngle: number;\n\tprivate smoothing: number;\n\tprivate lastX = 0;\n\tprivate lastY = 0;\n\tprivate baseBeta: number | null = null;\n\tprivate baseGamma: number | null = null;\n\n\t/**\n\t * @param onMove - Callback receiving normalized { x, y } values\n\t * @param axis - Restrict input to a single axis, or null for both\n\t * @param minAngle - Minimum device angle mapped to -1 (default: -45)\n\t * @param maxAngle - Maximum device angle mapped to 1 (default: 45)\n\t * @param smoothing - Exponential moving average factor 0-1 (default: 0.15, lower = smoother)\n\t */\n\tconstructor(onMove: SensorCallback, axis: Axis, minAngle = -45, maxAngle = 45, smoothing = 0.15) {\n\t\tthis.onMove = onMove;\n\t\tthis.axis = axis;\n\t\tthis.minAngle = minAngle;\n\t\tthis.maxAngle = maxAngle;\n\t\tthis.smoothing = smoothing;\n\t}\n\n\t/** Check if the DeviceOrientationEvent API is available in this environment. */\n\tstatic isSupported = (): boolean => {\n\t\treturn typeof window !== \"undefined\" && \"DeviceOrientationEvent\" in window;\n\t};\n\n\t/**\n\t * Check if explicit permission is required (iOS 13+).\n\t * On Android and desktop, this returns false.\n\t */\n\tstatic needsPermission = (): boolean => {\n\t\treturn (\n\t\t\ttypeof DeviceOrientationEvent !== \"undefined\" && \"requestPermission\" in DeviceOrientationEvent\n\t\t);\n\t};\n\n\t/**\n\t * Request permission to access device orientation.\n\t * On Android, resolves immediately to true.\n\t * On iOS 13+, triggers the native permission dialog.\n\t * Must be called from a user gesture on iOS.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!MotionSensor.isSupported()) return false;\n\n\t\tif (!MotionSensor.needsPermission()) {\n\t\t\tthis.permitted = true;\n\t\t\treturn true;\n\t\t}\n\n\t\ttry {\n\t\t\tconst DOE = DeviceOrientationEvent as unknown as {\n\t\t\t\trequestPermission: () => Promise<string>;\n\t\t\t};\n\t\t\tconst result = await DOE.requestPermission();\n\t\t\tthis.permitted = result === \"granted\";\n\t\t\treturn this.permitted;\n\t\t} catch {\n\t\t\tthis.permitted = false;\n\t\t\treturn false;\n\t\t}\n\t};\n\n\t/** Start listening to deviceorientation events. Requires prior permission. */\n\tstart = (): void => {\n\t\tif (this.active || !this.permitted) return;\n\t\tthis.active = true;\n\t\tthis.baseBeta = null;\n\t\tthis.baseGamma = null;\n\t\twindow.addEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Stop listening and remove the deviceorientation event listener. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\twindow.removeEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\t/**\n\t * Normalize device orientation angles to [-1, 1] and apply\n\t * exponential moving average smoothing.\n\t *\n\t * beta = X-axis rotation [-180, 180] (front-back tilt)\n\t * gamma = Y-axis rotation [-90, 90] (left-right tilt)\n\t */\n\tprivate handleOrientation = (e: Event): void => {\n\t\tconst evt = e as DeviceOrientationEvt;\n\t\tconst beta = evt.beta ?? 0;\n\t\tconst gamma = evt.gamma ?? 0;\n\n\t\t// Calibrate: capture the first reading as the neutral position\n\t\tif (this.baseBeta === null) {\n\t\t\tthis.baseBeta = beta;\n\t\t\tthis.baseGamma = gamma;\n\t\t}\n\n\t\t// Compute tilt relative to initial device position\n\t\tconst relativeBeta = beta - this.baseBeta;\n\t\tconst relativeGamma = gamma - (this.baseGamma as number);\n\n\t\tconst range = this.maxAngle - this.minAngle;\n\t\tconst rawX = this.axis === \"y\" ? 0 : this.clamp(relativeGamma / (range / 2));\n\t\tconst rawY = this.axis === \"x\" ? 0 : this.clamp(relativeBeta / (range / 2));\n\n\t\tthis.lastX = this.lastX + (rawX - this.lastX) * this.smoothing;\n\t\tthis.lastY = this.lastY + (rawY - this.lastY) * this.smoothing;\n\n\t\tthis.onMove({ x: this.lastX, y: this.lastY });\n\t};\n\n\t/** Clamp a value to the [-1, 1] range. */\n\tprivate clamp = (value: number): number => {\n\t\treturn Math.max(-1, Math.min(1, value));\n\t};\n}\n","import type { Axis } from \"../types.js\";\n\n/** Normalized sensor output with values in [-1, 1] range. */\nexport interface SensorOutput {\n\t/** Normalized X position [-1, 1] */\n\tx: number;\n\t/** Normalized Y position [-1, 1] */\n\ty: number;\n}\n\n/** Callback invoked when sensor detects movement. */\nexport type SensorCallback = (values: SensorOutput) => void;\n\n/**\n * Tracks pointer (mouse/touch) position over an element and normalizes\n * it to a [-1, 1] range relative to the element's bounds.\n *\n * Uses PointerEvent for unified mouse + touch input.\n */\nexport class PointerSensor {\n\tprivate eventsEl: HTMLElement;\n\tprivate onMove: SensorCallback;\n\tprivate onEnter: (() => void) | null;\n\tprivate onLeave: (() => void) | null;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate rect: DOMRect | null = null;\n\n\tconstructor(\n\t\tel: HTMLElement,\n\t\tonMove: SensorCallback,\n\t\tonEnter: (() => void) | null,\n\t\tonLeave: (() => void) | null,\n\t\taxis: Axis,\n\t\teventsEl: HTMLElement | null = null,\n\t) {\n\t\tthis.eventsEl = eventsEl ?? el;\n\t\tthis.onMove = onMove;\n\t\tthis.onEnter = onEnter;\n\t\tthis.onLeave = onLeave;\n\t\tthis.axis = axis;\n\t}\n\n\t/** Start listening to pointer events. */\n\tstart = (): void => {\n\t\tif (this.active) return;\n\t\tthis.active = true;\n\t\tthis.eventsEl.addEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.addEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.addEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.addEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.addEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.addEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Stop listening and remove all pointer event listeners. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\tthis.eventsEl.removeEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.removeEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.removeEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.removeEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.removeEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.removeEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\tprivate handlePointerDown = (e: PointerEvent): void => {\n\t\tthis.handlePointerEnter();\n\t\tthis.handlePointerMove(e);\n\t};\n\n\tprivate handlePointerUp = (): void => {\n\t\tthis.handlePointerLeave();\n\t};\n\n\t/**\n\t * Compute normalized [-1, 1] position from pointer coordinates\n\t * relative to the element's bounding rect. Respects axis locking.\n\t */\n\tprivate handlePointerMove = (e: PointerEvent): void => {\n\t\tif (!this.rect) {\n\t\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\t}\n\n\t\t// Use offsetWidth/Height for more stable dimensions (not affected by 3D transforms)\n\t\tconst width = this.eventsEl.offsetWidth || this.rect.width;\n\t\tconst height = this.eventsEl.offsetHeight || this.rect.height;\n\n\t\tconst rawX = (e.clientX - this.rect.left) / width;\n\t\tconst rawY = (e.clientY - this.rect.top) / height;\n\n\t\t// Clamp values if pointer is captured but outside bounds\n\t\tconst clampedX = Math.max(0, Math.min(1, rawX));\n\t\tconst clampedY = Math.max(0, Math.min(1, rawY));\n\n\t\tconst x = this.axis === \"y\" ? 0 : clampedX * 2 - 1;\n\t\tconst y = this.axis === \"x\" ? 0 : clampedY * 2 - 1;\n\n\t\tthis.onMove({ x, y });\n\t};\n\n\tprivate handlePointerEnter = (): void => {\n\t\t// Refresh rect on enter. If it's already in motion, this might be slightly off,\n\t\t// but it's still better than refreshing it on every move.\n\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\tthis.onEnter?.();\n\t};\n\n\tprivate handlePointerLeave = (): void => {\n\t\tthis.rect = null;\n\t\tthis.onLeave?.();\n\t};\n}\n","import { DEFAULT_OPTIONS } from \"./constants.js\";\nimport { GlareEffect } from \"./effects/glare.js\";\nimport { ShadowEffect } from \"./effects/shadow.js\";\nimport type { Layer } from \"./layers.js\";\nimport { cleanupLayers, scanLayers } from \"./layers.js\";\nimport { MotionSensor } from \"./sensors/motion.js\";\nimport type { SensorOutput } from \"./sensors/pointer.js\";\nimport { PointerSensor } from \"./sensors/pointer.js\";\nimport type {\n\tEventCallback,\n\tLevitaEventMap,\n\tLevitaOptions,\n\tTiltValues,\n\tUpdatableOptions,\n} from \"./types.js\";\n\n/**\n * Main entry point for the Levita 3D tilt effect.\n *\n * Orchestrates sensors (pointer, accelerometer), visual effects (glare, shadow),\n * and multi-layer parallax. All rendering is driven by CSS custom properties —\n * no requestAnimationFrame loop runs during interaction.\n *\n * @example\n * ```ts\n * import { Levita } from 'levita-js';\n * import 'levita-js/style.css';\n *\n * const tilt = new Levita(element, { glare: true, shadow: true });\n * tilt.on('move', ({ x, y }) => console.log(x, y));\n * ```\n */\nexport class Levita {\n\tprivate el: HTMLElement;\n\tprivate options: LevitaOptions;\n\tprivate pointerSensor: PointerSensor;\n\tprivate motionSensor: MotionSensor | null = null;\n\tprivate glareEffect: GlareEffect | null = null;\n\tprivate shadowEffect: ShadowEffect | null = null;\n\tprivate layers: Layer[] = [];\n\tprivate listeners = new Map<string, Set<EventCallback<unknown>>>();\n\tprivate destroyed = false;\n\tprivate gyroscopeRequested = false;\n\tprivate rafId: number | null = null;\n\n\t/**\n\t * @param el - The DOM element to apply the tilt effect to\n\t * @param options - Configuration options (all optional, sensible defaults)\n\t */\n\tconstructor(el: HTMLElement, options: Partial<LevitaOptions> = {}) {\n\t\tthis.el = el;\n\t\tthis.options = { ...DEFAULT_OPTIONS, ...options };\n\n\t\tthis.el.classList.add(\"levita\");\n\t\tthis.applyBaseProperties();\n\n\t\tthis.layers = scanLayers(this.el);\n\n\t\tif (this.options.glare) {\n\t\t\tthis.glareEffect = new GlareEffect(this.el, this.options.maxGlare);\n\t\t}\n\t\tif (this.options.shadow) {\n\t\t\tthis.shadowEffect = new ShadowEffect(this.el);\n\t\t}\n\n\t\tthis.pointerSensor = new PointerSensor(\n\t\t\tthis.el,\n\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t() => this.handleEnter(),\n\t\t\t() => this.handleLeave(),\n\t\t\tthis.options.axis,\n\t\t\tthis.options.eventsEl,\n\t\t);\n\n\t\tif (this.options.gyroscope !== false && MotionSensor.isSupported()) {\n\t\t\tthis.motionSensor = new MotionSensor(\n\t\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t\tthis.options.axis,\n\t\t\t);\n\n\t\t\tif (this.options.gyroscope === \"auto\") {\n\t\t\t\tthis.el.addEventListener(\"click\", this.handleFirstTouch, { once: true });\n\t\t\t}\n\t\t}\n\n\t\tif (!this.options.disabled) {\n\t\t\tthis.enable();\n\t\t}\n\t}\n\n\t/**\n\t * On first touch (iOS auto mode), request accelerometer permission\n\t * and switch from pointer to motion sensor if granted.\n\t */\n\tprivate handleFirstTouch = async (): Promise<void> => {\n\t\tif (this.destroyed || this.gyroscopeRequested || !this.motionSensor) return;\n\t\tthis.gyroscopeRequested = true;\n\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.pointerSensor.stop();\n\t\t\tthis.setTransition(false);\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t};\n\n\t/**\n\t * Update options at runtime without destroying/recreating the instance.\n\t *\n\t * Only \"lightweight\" options are supported — those that don't require\n\t * DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,\n\t * `disabled`, and `maxGlare` require a full destroy/recreate cycle\n\t * (or use `enable()`/`disable()` for toggling the effect).\n\t */\n\tupdate = (options: Partial<UpdatableOptions>): void => {\n\t\tif (this.destroyed) return;\n\t\tObject.assign(this.options, options);\n\t\tthis.applyBaseProperties();\n\n\t\tif (options.axis !== undefined) {\n\t\t\tthis.pointerSensor.setAxis(options.axis);\n\t\t\tthis.motionSensor?.setAxis(options.axis);\n\t\t}\n\t};\n\n\t/** Re-enable the tilt effect after a `disable()` call. */\n\tenable = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.options.disabled = false;\n\t\tthis.pointerSensor.start();\n\t};\n\n\t/** Pause the tilt effect and reset the element to its neutral position. */\n\tdisable = (): void => {\n\t\tthis.options.disabled = true;\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.resetTransform();\n\t};\n\n\t/**\n\t * Manually request accelerometer permission (for `gyroscope: true` mode).\n\t * Must be called from a user gesture on iOS 13+.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!this.motionSensor) return false;\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.pointerSensor.stop();\n\t\t\tthis.setTransition(false);\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t\treturn granted;\n\t};\n\n\t/**\n\t * Fully clean up: stop sensors, remove effects, restore the element\n\t * to its original state. The instance cannot be reused after this.\n\t */\n\tdestroy = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.destroyed = true;\n\n\t\tif (this.rafId) cancelAnimationFrame(this.rafId);\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.glareEffect?.destroy();\n\t\tthis.shadowEffect?.destroy();\n\t\tcleanupLayers(this.layers);\n\n\t\tthis.el.classList.remove(\"levita\");\n\t\tthis.removeBaseProperties();\n\t\tthis.listeners.clear();\n\n\t\tthis.el.removeEventListener(\"click\", this.handleFirstTouch);\n\t};\n\n\t/**\n\t * Register an event listener.\n\t *\n\t * @param event - Event name: 'move', 'enter', or 'leave'\n\t * @param callback - Handler function\n\t */\n\ton = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)?.add(callback as EventCallback<unknown>);\n\t};\n\n\t/**\n\t * Remove a previously registered event listener.\n\t *\n\t * @param event - Event name\n\t * @param callback - The exact handler reference passed to `on()`\n\t */\n\toff = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tthis.listeners.get(event)?.delete(callback as EventCallback<unknown>);\n\t};\n\n\t/** Emit an event to all registered listeners. */\n\tprivate emit = <K extends keyof LevitaEventMap>(event: K, data: LevitaEventMap[K]): void => {\n\t\tconst callbacks = this.listeners.get(event);\n\t\tif (callbacks) {\n\t\t\tfor (const cb of callbacks) {\n\t\t\t\tcb(data);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Process normalized sensor input and update CSS custom properties.\n\t * Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).\n\t *\n\t * Note: We use requestAnimationFrame to throttle updates. High-polling rate mice\n\t * can fire hundreds of events per second, which would saturate the main thread\n\t * with CSS variable updates and style recalculations. This ensures we only\n\t * update the DOM once per browser frame.\n\t */\n\tprivate handleSensorInput = (input: SensorOutput): void => {\n\t\tif (this.options.disabled) return;\n\n\t\tconst multiplier = this.options.reverse ? -1 : 1;\n\t\tconst x = input.y * this.options.max * multiplier;\n\t\tconst y = input.x * this.options.max * multiplier * -1;\n\n\t\t// Cancel any pending update from the same frame to avoid style thrashing\n\t\tif (this.rafId) cancelAnimationFrame(this.rafId);\n\n\t\tthis.rafId = requestAnimationFrame(() => {\n\t\t\tthis.el.style.setProperty(\"--levita-x\", `${x}deg`);\n\t\t\tthis.el.style.setProperty(\"--levita-y\", `${y}deg`);\n\t\t\tthis.el.style.setProperty(\"--levita-percent-x\", String(input.x));\n\t\t\tthis.el.style.setProperty(\"--levita-percent-y\", String(input.y));\n\n\t\t\tthis.glareEffect?.update(input.x, input.y);\n\t\t\tthis.shadowEffect?.update(input.x, input.y);\n\n\t\t\tconst values: TiltValues = {\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tpercentX: input.x,\n\t\t\t\tpercentY: input.y,\n\t\t\t};\n\t\t\tthis.emit(\"move\", values);\n\n\t\t\tthis.rafId = null;\n\t\t});\n\t};\n\n\tprivate handleEnter = (): void => {\n\t\tthis.setTransition(false);\n\t\tthis.el.style.setProperty(\"--levita-scale\", String(this.options.scale));\n\t\tthis.emit(\"enter\", undefined);\n\t};\n\n\tprivate handleLeave = (): void => {\n\t\tif (this.rafId) {\n\t\t\tcancelAnimationFrame(this.rafId);\n\t\t\tthis.rafId = null;\n\t\t}\n\t\tthis.setTransition(true);\n\t\tif (this.options.reset) {\n\t\t\tthis.resetTransform();\n\t\t}\n\t\tthis.emit(\"leave\", undefined);\n\t};\n\n\t/** Toggle the CSS transition on or off. */\n\tprivate setTransition = (on: boolean): void => {\n\t\tthis.el.style.setProperty(\"--levita-speed\", on ? `${this.options.speed}ms` : \"0ms\");\n\t};\n\n\t/** Reset the element to its neutral (non-tilted) position. */\n\tprivate resetTransform = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-x\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-y\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-scale\", \"1\");\n\t\tthis.el.style.setProperty(\"--levita-percent-x\", \"0\");\n\t\tthis.el.style.setProperty(\"--levita-percent-y\", \"0\");\n\t\tthis.glareEffect?.update(0, 0);\n\t\tthis.shadowEffect?.update(0, 0);\n\t};\n\n\t/** Apply initial CSS custom properties from options. */\n\tprivate applyBaseProperties = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-perspective\", `${this.options.perspective}px`);\n\t\tthis.el.style.setProperty(\"--levita-speed\", `${this.options.speed}ms`);\n\t\tthis.el.style.setProperty(\"--levita-easing\", this.options.easing);\n\t};\n\n\t/** Remove all Levita CSS custom properties from the element. */\n\tprivate removeBaseProperties = (): void => {\n\t\tthis.el.style.removeProperty(\"--levita-perspective\");\n\t\tthis.el.style.removeProperty(\"--levita-speed\");\n\t\tthis.el.style.removeProperty(\"--levita-easing\");\n\t\tthis.el.style.removeProperty(\"--levita-x\");\n\t\tthis.el.style.removeProperty(\"--levita-y\");\n\t\tthis.el.style.removeProperty(\"--levita-scale\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-x\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-y\");\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/effects/glare.ts","../src/effects/shadow.ts","../src/layers.ts","../src/sensors/motion.ts","../src/sensors/pointer.ts","../src/levita.ts"],"sourcesContent":["import type { LevitaOptions } from \"./types.js\";\n\n/** All keys of `LevitaOptions`, derived from `DEFAULT_OPTIONS`. */\nexport const OPTION_KEYS: readonly (keyof LevitaOptions)[] = [\n\t\"max\",\n\t\"perspective\",\n\t\"scale\",\n\t\"speed\",\n\t\"easing\",\n\t\"reverse\",\n\t\"axis\",\n\t\"reset\",\n\t\"glare\",\n\t\"maxGlare\",\n\t\"shadow\",\n\t\"gyroscope\",\n\t\"disabled\",\n\t\"eventsEl\",\n] as const;\n\n/**\n * Build a partial `LevitaOptions` object from a source,\n * including only keys that are explicitly defined.\n */\nexport const buildOptions = (source: Partial<LevitaOptions>): Partial<LevitaOptions> => {\n\tconst options: Partial<LevitaOptions> = {};\n\tfor (const key of OPTION_KEYS) {\n\t\tif (source[key] !== undefined) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: generic key assignment\n\t\t\t(options as any)[key] = source[key];\n\t\t}\n\t}\n\treturn options;\n};\n\nexport const DEFAULT_OPTIONS: LevitaOptions = {\n\tmax: 15,\n\tperspective: 1000,\n\tscale: 1.05,\n\tspeed: 200,\n\teasing: \"ease-out\",\n\treverse: false,\n\taxis: null,\n\treset: true,\n\tglare: false,\n\tmaxGlare: 0.5,\n\tshadow: false,\n\tgyroscope: \"auto\",\n\tdisabled: false,\n\teventsEl: null,\n};\n","/**\n * Creates a radial gradient overlay that follows the tilt position,\n * simulating light reflection on the surface.\n *\n * Injects two DOM elements (.levita-glare > .levita-glare-inner) into\n * the target element. Position and opacity are driven by CSS custom\n * properties — no JS runs per animation frame.\n */\nexport class GlareEffect {\n\tprivate container: HTMLElement;\n\tprivate inner: HTMLElement;\n\tprivate maxOpacity: number;\n\n\t/**\n\t * @param el - The element to attach the glare overlay to\n\t * @param maxOpacity - Maximum glare opacity (0-1)\n\t */\n\tconstructor(el: HTMLElement, maxOpacity: number) {\n\t\tthis.maxOpacity = maxOpacity;\n\n\t\tthis.container = document.createElement(\"div\");\n\t\tthis.container.classList.add(\"levita-glare\");\n\n\t\tthis.inner = document.createElement(\"div\");\n\t\tthis.inner.classList.add(\"levita-glare-inner\");\n\n\t\tthis.container.appendChild(this.inner);\n\t\tel.appendChild(this.container);\n\n\t\tif (!el.style.position || el.style.position === \"static\") {\n\t\t\tel.style.position = \"relative\";\n\t\t}\n\t}\n\n\t/**\n\t * Update glare position and intensity based on normalized tilt values.\n\t * Sets CSS custom properties that the stylesheet uses for rendering.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst glareX = ((normalizedX + 1) / 2) * 100;\n\t\tconst glareY = ((normalizedY + 1) / 2) * 100;\n\t\tconst intensity = Math.sqrt(normalizedX ** 2 + normalizedY ** 2) / Math.SQRT2;\n\n\t\tthis.inner.style.setProperty(\"--levita-glare-x\", `${glareX}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-y\", `${glareY}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-opacity\", `${intensity * this.maxOpacity}`);\n\t};\n\n\t/** Remove the glare DOM elements from the parent. */\n\tdestroy = (): void => {\n\t\tthis.container.remove();\n\t};\n}\n","/**\n * Adds a dynamic drop shadow that shifts based on the tilt angle,\n * reinforcing the 3D depth illusion.\n *\n * Uses CSS custom properties (--levita-shadow-x, --levita-shadow-y)\n * combined with `filter: drop-shadow()` — no JS runs per animation frame.\n */\nexport class ShadowEffect {\n\tprivate el: HTMLElement;\n\tprivate maxOffset: number;\n\n\t/**\n\t * @param el - The element to apply the shadow to\n\t * @param maxOffset - Maximum shadow offset in pixels (default: 20)\n\t */\n\tconstructor(el: HTMLElement, maxOffset = 20) {\n\t\tthis.el = el;\n\t\tthis.maxOffset = maxOffset;\n\t\tthis.el.classList.add(\"levita-shadow\");\n\t}\n\n\t/**\n\t * Update shadow offset based on normalized tilt values.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst shadowX = normalizedX * this.maxOffset;\n\t\tconst shadowY = normalizedY * this.maxOffset;\n\n\t\tthis.el.style.setProperty(\"--levita-shadow-x\", `${shadowX}px`);\n\t\tthis.el.style.setProperty(\"--levita-shadow-y\", `${shadowY}px`);\n\t};\n\n\t/** Remove the shadow class and clean up CSS custom properties. */\n\tdestroy = (): void => {\n\t\tthis.el.classList.remove(\"levita-shadow\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-x\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-y\");\n\t};\n}\n","/** Represents a child element with a parallax depth offset. */\nexport interface Layer {\n\t/** The DOM element */\n\tel: HTMLElement;\n\t/** The depth offset value (positive = forward, negative = back) */\n\toffset: number;\n}\n\n/**\n * Scan a container for children with `data-levita-offset` attributes\n * and set the `--levita-offset` CSS custom property on each one.\n *\n * The CSS stylesheet uses this variable with `translateZ()` to position\n * layers at different depths — no JS runs per animation frame.\n *\n * @param container - The parent element to scan for layer children\n * @returns Array of discovered layers with their elements and offsets\n */\nexport const scanLayers = (container: HTMLElement): Layer[] => {\n\tconst elements = container.querySelectorAll<HTMLElement>(\"[data-levita-offset]\");\n\tconst layers: Layer[] = [];\n\n\tfor (const el of elements) {\n\t\tconst raw = el.dataset.levitaOffset;\n\t\tconst offset = Number.parseFloat(raw ?? \"0\");\n\t\tif (!Number.isNaN(offset)) {\n\t\t\tel.style.setProperty(\"--levita-offset\", String(offset));\n\t\t\tlayers.push({ el, offset });\n\t\t}\n\t}\n\n\treturn layers;\n};\n\n/**\n * Remove the `--levita-offset` CSS custom property from all layer elements.\n *\n * @param layers - The layers to clean up\n */\nexport const cleanupLayers = (layers: Layer[]): void => {\n\tfor (const layer of layers) {\n\t\tlayer.el.style.removeProperty(\"--levita-offset\");\n\t}\n};\n","import type { Axis } from \"../types.js\";\nimport type { SensorCallback } from \"./pointer.js\";\n\ninterface DeviceOrientationEvt extends Event {\n\tbeta: number | null;\n\tgamma: number | null;\n}\n\n/**\n * Reads device orientation (accelerometer/gyroscope) and normalizes\n * the tilt angles to a [-1, 1] range.\n *\n * Handles iOS 13+ permission flow via async `requestPermission()`.\n * On Android, permission is granted automatically.\n *\n * Uses exponential moving average for smoothing raw sensor data.\n */\nexport class MotionSensor {\n\tprivate onMove: SensorCallback;\n\tprivate onFirstEvent: (() => void) | null;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate permitted = false;\n\tprivate receivedEvent = false;\n\tprivate warmup = false;\n\tprivate minAngle: number;\n\tprivate maxAngle: number;\n\tprivate smoothing: number;\n\tprivate lastX = 0;\n\tprivate lastY = 0;\n\tprivate baseBeta: number | null = null;\n\tprivate baseGamma: number | null = null;\n\n\t/**\n\t * @param onMove - Callback receiving normalized { x, y } values\n\t * @param axis - Restrict input to a single axis, or null for both\n\t * @param minAngle - Minimum device angle mapped to -1 (default: -45)\n\t * @param maxAngle - Maximum device angle mapped to 1 (default: 45)\n\t * @param smoothing - Exponential moving average factor 0-1 (default: 0.15, lower = smoother)\n\t * @param onFirstEvent - Called once when the first valid deviceorientation event is received\n\t */\n\tconstructor(\n\t\tonMove: SensorCallback,\n\t\taxis: Axis,\n\t\tminAngle = -45,\n\t\tmaxAngle = 45,\n\t\tsmoothing = 0.15,\n\t\tonFirstEvent: (() => void) | null = null,\n\t) {\n\t\tthis.onMove = onMove;\n\t\tthis.axis = axis;\n\t\tthis.minAngle = minAngle;\n\t\tthis.maxAngle = maxAngle;\n\t\tthis.smoothing = smoothing;\n\t\tthis.onFirstEvent = onFirstEvent;\n\t}\n\n\t/** Check if the DeviceOrientationEvent API is available in this environment. */\n\tstatic isSupported = (): boolean => {\n\t\treturn typeof window !== \"undefined\" && \"DeviceOrientationEvent\" in window;\n\t};\n\n\t/**\n\t * Check if explicit permission is required (iOS 13+).\n\t * On Android and desktop, this returns false.\n\t */\n\tstatic needsPermission = (): boolean => {\n\t\treturn (\n\t\t\ttypeof DeviceOrientationEvent !== \"undefined\" && \"requestPermission\" in DeviceOrientationEvent\n\t\t);\n\t};\n\n\t/**\n\t * Request permission to access device orientation.\n\t * On Android, resolves immediately to true.\n\t * On iOS 13+, triggers the native permission dialog.\n\t * Must be called from a user gesture on iOS.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!MotionSensor.isSupported()) return false;\n\n\t\tif (!MotionSensor.needsPermission()) {\n\t\t\tthis.permitted = true;\n\t\t\treturn true;\n\t\t}\n\n\t\ttry {\n\t\t\tconst DOE = DeviceOrientationEvent as unknown as {\n\t\t\t\trequestPermission: () => Promise<string>;\n\t\t\t};\n\t\t\tconst result = await DOE.requestPermission();\n\t\t\tthis.permitted = result === \"granted\";\n\t\t\treturn this.permitted;\n\t\t} catch {\n\t\t\tthis.permitted = false;\n\t\t\treturn false;\n\t\t}\n\t};\n\n\t/** Start listening to deviceorientation events. Requires prior permission. */\n\tstart = (): void => {\n\t\tif (this.active || !this.permitted) return;\n\t\tthis.active = true;\n\t\tthis.receivedEvent = false;\n\t\tthis.warmup = false;\n\t\tthis.baseBeta = null;\n\t\tthis.baseGamma = null;\n\t\twindow.addEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Stop listening and remove the deviceorientation event listener. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\twindow.removeEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\t/**\n\t * Normalize device orientation angles to [-1, 1] and apply\n\t * exponential moving average smoothing.\n\t *\n\t * beta = X-axis rotation [-180, 180] (front-back tilt)\n\t * gamma = Y-axis rotation [-90, 90] (left-right tilt)\n\t */\n\tprivate handleOrientation = (e: Event): void => {\n\t\tconst evt = e as DeviceOrientationEvt;\n\t\tif (evt.beta === null && evt.gamma === null) return;\n\n\t\t// Skip first event — some Android browsers fire stale sensor data\n\t\t// that doesn't reflect the actual device position.\n\t\tif (!this.warmup) {\n\t\t\tthis.warmup = true;\n\t\t\treturn;\n\t\t}\n\n\t\tconst beta = evt.beta ?? 0;\n\t\tconst gamma = evt.gamma ?? 0;\n\n\t\t// Notify on first valid event (used for pointer→motion handoff)\n\t\tif (!this.receivedEvent) {\n\t\t\tthis.receivedEvent = true;\n\t\t\tthis.onFirstEvent?.();\n\t\t}\n\n\t\t// Calibrate: capture the first reading as the neutral position\n\t\tif (this.baseBeta === null) {\n\t\t\tthis.baseBeta = beta;\n\t\t\tthis.baseGamma = gamma;\n\t\t}\n\n\t\t// Compute tilt relative to initial device position\n\t\tconst relativeBeta = beta - this.baseBeta;\n\t\tconst relativeGamma = gamma - (this.baseGamma as number);\n\n\t\tconst range = this.maxAngle - this.minAngle;\n\t\tconst rawX = this.axis === \"y\" ? 0 : this.clamp(relativeGamma / (range / 2));\n\t\tconst rawY = this.axis === \"x\" ? 0 : this.clamp(relativeBeta / (range / 2));\n\n\t\tthis.lastX = this.lastX + (rawX - this.lastX) * this.smoothing;\n\t\tthis.lastY = this.lastY + (rawY - this.lastY) * this.smoothing;\n\n\t\tthis.onMove({ x: this.lastX, y: this.lastY });\n\t};\n\n\t/** Clamp a value to the [-1, 1] range. */\n\tprivate clamp = (value: number): number => {\n\t\treturn Math.max(-1, Math.min(1, value));\n\t};\n}\n","import type { Axis } from \"../types.js\";\n\n/** Normalized sensor output with values in [-1, 1] range. */\nexport interface SensorOutput {\n\t/** Normalized X position [-1, 1] */\n\tx: number;\n\t/** Normalized Y position [-1, 1] */\n\ty: number;\n}\n\n/** Callback invoked when sensor detects movement. */\nexport type SensorCallback = (values: SensorOutput) => void;\n\n/**\n * Tracks pointer (mouse/touch) position over an element and normalizes\n * it to a [-1, 1] range relative to the element's bounds.\n *\n * Uses PointerEvent for unified mouse + touch input.\n */\nexport class PointerSensor {\n\tprivate eventsEl: HTMLElement;\n\tprivate onMove: SensorCallback;\n\tprivate onEnter: (() => void) | null;\n\tprivate onLeave: (() => void) | null;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate rect: DOMRect | null = null;\n\n\tconstructor(\n\t\tel: HTMLElement,\n\t\tonMove: SensorCallback,\n\t\tonEnter: (() => void) | null,\n\t\tonLeave: (() => void) | null,\n\t\taxis: Axis,\n\t\teventsEl: HTMLElement | null = null,\n\t) {\n\t\tthis.eventsEl = eventsEl ?? el;\n\t\tthis.onMove = onMove;\n\t\tthis.onEnter = onEnter;\n\t\tthis.onLeave = onLeave;\n\t\tthis.axis = axis;\n\t}\n\n\t/** Start listening to pointer events. */\n\tstart = (): void => {\n\t\tif (this.active) return;\n\t\tthis.active = true;\n\t\tthis.eventsEl.addEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.addEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.addEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.addEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.addEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.addEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Stop listening and remove all pointer event listeners. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\tthis.eventsEl.removeEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.removeEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.removeEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.removeEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.removeEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.removeEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\tprivate handlePointerDown = (e: PointerEvent): void => {\n\t\tthis.handlePointerEnter();\n\t\tthis.handlePointerMove(e);\n\t};\n\n\tprivate handlePointerUp = (): void => {\n\t\tthis.handlePointerLeave();\n\t};\n\n\t/**\n\t * Compute normalized [-1, 1] position from pointer coordinates\n\t * relative to the element's bounding rect. Respects axis locking.\n\t */\n\tprivate handlePointerMove = (e: PointerEvent): void => {\n\t\tif (!this.rect) {\n\t\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\t}\n\n\t\t// Use offsetWidth/Height for more stable dimensions (not affected by 3D transforms)\n\t\tconst width = this.eventsEl.offsetWidth || this.rect.width;\n\t\tconst height = this.eventsEl.offsetHeight || this.rect.height;\n\n\t\tconst rawX = (e.clientX - this.rect.left) / width;\n\t\tconst rawY = (e.clientY - this.rect.top) / height;\n\n\t\t// Clamp values if pointer is captured but outside bounds\n\t\tconst clampedX = Math.max(0, Math.min(1, rawX));\n\t\tconst clampedY = Math.max(0, Math.min(1, rawY));\n\n\t\tconst x = this.axis === \"y\" ? 0 : clampedX * 2 - 1;\n\t\tconst y = this.axis === \"x\" ? 0 : clampedY * 2 - 1;\n\n\t\tthis.onMove({ x, y });\n\t};\n\n\tprivate handlePointerEnter = (): void => {\n\t\t// Refresh rect on enter. If it's already in motion, this might be slightly off,\n\t\t// but it's still better than refreshing it on every move.\n\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\tthis.onEnter?.();\n\t};\n\n\tprivate handlePointerLeave = (): void => {\n\t\tthis.rect = null;\n\t\tthis.onLeave?.();\n\t};\n}\n","import { DEFAULT_OPTIONS } from \"./constants.js\";\nimport { GlareEffect } from \"./effects/glare.js\";\nimport { ShadowEffect } from \"./effects/shadow.js\";\nimport type { Layer } from \"./layers.js\";\nimport { cleanupLayers, scanLayers } from \"./layers.js\";\nimport { MotionSensor } from \"./sensors/motion.js\";\nimport type { SensorOutput } from \"./sensors/pointer.js\";\nimport { PointerSensor } from \"./sensors/pointer.js\";\nimport type {\n\tEventCallback,\n\tLevitaEventMap,\n\tLevitaOptions,\n\tTiltValues,\n\tUpdatableOptions,\n} from \"./types.js\";\n\n/**\n * Main entry point for the Levita 3D tilt effect.\n *\n * Orchestrates sensors (pointer, accelerometer), visual effects (glare, shadow),\n * and multi-layer parallax. All rendering is driven by CSS custom properties —\n * no requestAnimationFrame loop runs during interaction.\n *\n * @example\n * ```ts\n * import { Levita } from 'levita-js';\n * import 'levita-js/style.css';\n *\n * const tilt = new Levita(element, { glare: true, shadow: true });\n * tilt.on('move', ({ x, y }) => console.log(x, y));\n * ```\n */\nexport class Levita {\n\tprivate el: HTMLElement;\n\tprivate options: LevitaOptions;\n\tprivate pointerSensor: PointerSensor;\n\tprivate motionSensor: MotionSensor | null = null;\n\tprivate glareEffect: GlareEffect | null = null;\n\tprivate shadowEffect: ShadowEffect | null = null;\n\tprivate layers: Layer[] = [];\n\tprivate listeners = new Map<string, Set<EventCallback<unknown>>>();\n\tprivate destroyed = false;\n\tprivate gyroscopeRequested = false;\n\tprivate gyroscopeEvent: string | null = null;\n\tprivate rafId: number | null = null;\n\n\t/**\n\t * @param el - The DOM element to apply the tilt effect to\n\t * @param options - Configuration options (all optional, sensible defaults)\n\t */\n\tconstructor(el: HTMLElement, options: Partial<LevitaOptions> = {}) {\n\t\tthis.el = el;\n\t\tthis.options = { ...DEFAULT_OPTIONS, ...options };\n\n\t\tthis.el.classList.add(\"levita\");\n\t\tthis.applyBaseProperties();\n\n\t\tthis.layers = scanLayers(this.el);\n\n\t\tif (this.options.glare) {\n\t\t\tthis.glareEffect = new GlareEffect(this.el, this.options.maxGlare);\n\t\t}\n\t\tif (this.options.shadow) {\n\t\t\tthis.shadowEffect = new ShadowEffect(this.el);\n\t\t}\n\n\t\tthis.pointerSensor = new PointerSensor(\n\t\t\tthis.el,\n\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t() => this.handleEnter(),\n\t\t\t() => this.handleLeave(),\n\t\t\tthis.options.axis,\n\t\t\tthis.options.eventsEl,\n\t\t);\n\n\t\tif (this.options.gyroscope !== false && MotionSensor.isSupported()) {\n\t\t\tthis.motionSensor = new MotionSensor(\n\t\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t\tthis.options.axis,\n\t\t\t\tundefined,\n\t\t\t\tundefined,\n\t\t\t\tundefined,\n\t\t\t\t() => this.handleMotionReady(),\n\t\t\t);\n\n\t\t\tif (this.options.gyroscope === \"auto\") {\n\t\t\t\t// iOS requires \"click\" for DeviceOrientationEvent.requestPermission(),\n\t\t\t\t// Android fires \"pointerup\" more reliably on touch interactions.\n\t\t\t\tthis.gyroscopeEvent = MotionSensor.needsPermission() ? \"click\" : \"pointerup\";\n\t\t\t\tthis.el.addEventListener(this.gyroscopeEvent, this.handleFirstTouch, { once: true });\n\t\t\t}\n\t\t}\n\n\t\tif (!this.options.disabled) {\n\t\t\tthis.enable();\n\t\t}\n\t}\n\n\t/**\n\t * On first interaction, request accelerometer permission and start the\n\t * motion sensor. The pointer sensor keeps running until the first valid\n\t * deviceorientation event is received (see handleMotionReady).\n\t */\n\tprivate handleFirstTouch = async (): Promise<void> => {\n\t\tif (this.destroyed || this.gyroscopeRequested || !this.motionSensor) return;\n\t\tthis.gyroscopeRequested = true;\n\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t};\n\n\t/**\n\t * Called when the motion sensor receives its first valid event.\n\t * At this point it's safe to hand off from pointer to motion.\n\t */\n\tprivate handleMotionReady = (): void => {\n\t\tthis.pointerSensor.stop();\n\t\tthis.setTransition(false);\n\t};\n\n\t/**\n\t * Update options at runtime without destroying/recreating the instance.\n\t *\n\t * Only \"lightweight\" options are supported — those that don't require\n\t * DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,\n\t * `disabled`, and `maxGlare` require a full destroy/recreate cycle\n\t * (or use `enable()`/`disable()` for toggling the effect).\n\t */\n\tupdate = (options: Partial<UpdatableOptions>): void => {\n\t\tif (this.destroyed) return;\n\t\tObject.assign(this.options, options);\n\t\tthis.applyBaseProperties();\n\n\t\tif (options.axis !== undefined) {\n\t\t\tthis.pointerSensor.setAxis(options.axis);\n\t\t\tthis.motionSensor?.setAxis(options.axis);\n\t\t}\n\t};\n\n\t/** Re-enable the tilt effect after a `disable()` call. */\n\tenable = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.options.disabled = false;\n\t\tthis.pointerSensor.start();\n\t};\n\n\t/** Pause the tilt effect and reset the element to its neutral position. */\n\tdisable = (): void => {\n\t\tthis.options.disabled = true;\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.resetTransform();\n\t};\n\n\t/**\n\t * Manually request accelerometer permission (for `gyroscope: true` mode).\n\t * Must be called from a user gesture on iOS 13+.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!this.motionSensor) return false;\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t\treturn granted;\n\t};\n\n\t/**\n\t * Fully clean up: stop sensors, remove effects, restore the element\n\t * to its original state. The instance cannot be reused after this.\n\t */\n\tdestroy = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.destroyed = true;\n\n\t\tif (this.rafId) cancelAnimationFrame(this.rafId);\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.glareEffect?.destroy();\n\t\tthis.shadowEffect?.destroy();\n\t\tcleanupLayers(this.layers);\n\n\t\tthis.el.classList.remove(\"levita\");\n\t\tthis.removeBaseProperties();\n\t\tthis.listeners.clear();\n\n\t\tif (this.gyroscopeEvent) {\n\t\t\tthis.el.removeEventListener(this.gyroscopeEvent, this.handleFirstTouch);\n\t\t}\n\t};\n\n\t/**\n\t * Register an event listener.\n\t *\n\t * @param event - Event name: 'move', 'enter', or 'leave'\n\t * @param callback - Handler function\n\t */\n\ton = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)?.add(callback as EventCallback<unknown>);\n\t};\n\n\t/**\n\t * Remove a previously registered event listener.\n\t *\n\t * @param event - Event name\n\t * @param callback - The exact handler reference passed to `on()`\n\t */\n\toff = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tthis.listeners.get(event)?.delete(callback as EventCallback<unknown>);\n\t};\n\n\t/** Emit an event to all registered listeners. */\n\tprivate emit = <K extends keyof LevitaEventMap>(event: K, data: LevitaEventMap[K]): void => {\n\t\tconst callbacks = this.listeners.get(event);\n\t\tif (callbacks) {\n\t\t\tfor (const cb of callbacks) {\n\t\t\t\tcb(data);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Process normalized sensor input and update CSS custom properties.\n\t * Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).\n\t *\n\t * Note: We use requestAnimationFrame to throttle updates. High-polling rate mice\n\t * can fire hundreds of events per second, which would saturate the main thread\n\t * with CSS variable updates and style recalculations. This ensures we only\n\t * update the DOM once per browser frame.\n\t */\n\tprivate handleSensorInput = (input: SensorOutput): void => {\n\t\tif (this.options.disabled) return;\n\n\t\tconst multiplier = this.options.reverse ? -1 : 1;\n\t\tconst x = input.y * this.options.max * multiplier;\n\t\tconst y = input.x * this.options.max * multiplier * -1;\n\n\t\t// Cancel any pending update from the same frame to avoid style thrashing\n\t\tif (this.rafId) cancelAnimationFrame(this.rafId);\n\n\t\tthis.rafId = requestAnimationFrame(() => {\n\t\t\tthis.el.style.setProperty(\"--levita-x\", `${x}deg`);\n\t\t\tthis.el.style.setProperty(\"--levita-y\", `${y}deg`);\n\t\t\tthis.el.style.setProperty(\"--levita-percent-x\", String(input.x));\n\t\t\tthis.el.style.setProperty(\"--levita-percent-y\", String(input.y));\n\n\t\t\tthis.glareEffect?.update(input.x, input.y);\n\t\t\tthis.shadowEffect?.update(input.x, input.y);\n\n\t\t\tconst values: TiltValues = {\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tpercentX: input.x,\n\t\t\t\tpercentY: input.y,\n\t\t\t};\n\t\t\tthis.emit(\"move\", values);\n\n\t\t\tthis.rafId = null;\n\t\t});\n\t};\n\n\tprivate handleEnter = (): void => {\n\t\tthis.setTransition(false);\n\t\tthis.el.style.setProperty(\"--levita-scale\", String(this.options.scale));\n\t\tthis.emit(\"enter\", undefined);\n\t};\n\n\tprivate handleLeave = (): void => {\n\t\tif (this.rafId) {\n\t\t\tcancelAnimationFrame(this.rafId);\n\t\t\tthis.rafId = null;\n\t\t}\n\t\tthis.setTransition(true);\n\t\tif (this.options.reset) {\n\t\t\tthis.resetTransform();\n\t\t}\n\t\tthis.emit(\"leave\", undefined);\n\t};\n\n\t/** Toggle the CSS transition on or off. */\n\tprivate setTransition = (on: boolean): void => {\n\t\tthis.el.style.setProperty(\"--levita-speed\", on ? `${this.options.speed}ms` : \"0ms\");\n\t};\n\n\t/** Reset the element to its neutral (non-tilted) position. */\n\tprivate resetTransform = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-x\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-y\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-scale\", \"1\");\n\t\tthis.el.style.setProperty(\"--levita-percent-x\", \"0\");\n\t\tthis.el.style.setProperty(\"--levita-percent-y\", \"0\");\n\t\tthis.glareEffect?.update(0, 0);\n\t\tthis.shadowEffect?.update(0, 0);\n\t};\n\n\t/** Apply initial CSS custom properties from options. */\n\tprivate applyBaseProperties = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-perspective\", `${this.options.perspective}px`);\n\t\tthis.el.style.setProperty(\"--levita-speed\", `${this.options.speed}ms`);\n\t\tthis.el.style.setProperty(\"--levita-easing\", this.options.easing);\n\t};\n\n\t/** Remove all Levita CSS custom properties from the element. */\n\tprivate removeBaseProperties = (): void => {\n\t\tthis.el.style.removeProperty(\"--levita-perspective\");\n\t\tthis.el.style.removeProperty(\"--levita-speed\");\n\t\tthis.el.style.removeProperty(\"--levita-easing\");\n\t\tthis.el.style.removeProperty(\"--levita-x\");\n\t\tthis.el.style.removeProperty(\"--levita-y\");\n\t\tthis.el.style.removeProperty(\"--levita-scale\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-x\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-y\");\n\t};\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "levita-js",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Lightweight 3D tilt & parallax with accelerometer support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",