@tweenjs/tween.js 19.0.0 → 20.0.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/tween.esm.js CHANGED
@@ -213,37 +213,7 @@ var Easing = Object.freeze({
213
213
  },
214
214
  });
215
215
 
216
- var now;
217
- // Include a performance.now polyfill.
218
- // In node.js, use process.hrtime.
219
- // eslint-disable-next-line
220
- // @ts-ignore
221
- if (typeof self === 'undefined' && typeof process !== 'undefined' && process.hrtime) {
222
- now = function () {
223
- // eslint-disable-next-line
224
- // @ts-ignore
225
- var time = process.hrtime();
226
- // Convert [seconds, nanoseconds] to milliseconds.
227
- return time[0] * 1000 + time[1] / 1000000;
228
- };
229
- }
230
- // In a browser, use self.performance.now if it is available.
231
- else if (typeof self !== 'undefined' && self.performance !== undefined && self.performance.now !== undefined) {
232
- // This must be bound, because directly assigning this function
233
- // leads to an invocation exception in Chrome.
234
- now = self.performance.now.bind(self.performance);
235
- }
236
- // Use Date.now if it is available.
237
- else if (Date.now !== undefined) {
238
- now = Date.now;
239
- }
240
- // Otherwise, use 'new Date().getTime()'.
241
- else {
242
- now = function () {
243
- return new Date().getTime();
244
- };
245
- }
246
- var now$1 = now;
216
+ var now = function () { return performance.now(); };
247
217
 
248
218
  /**
249
219
  * Controlling groups of tweens
@@ -274,7 +244,7 @@ var Group = /** @class */ (function () {
274
244
  delete this._tweensAddedDuringUpdate[tween.getId()];
275
245
  };
276
246
  Group.prototype.update = function (time, preserve) {
277
- if (time === void 0) { time = now$1(); }
247
+ if (time === void 0) { time = now(); }
278
248
  if (preserve === void 0) { preserve = false; }
279
249
  var tweenIds = Object.keys(this._tweens);
280
250
  if (tweenIds.length === 0) {
@@ -415,6 +385,7 @@ var Tween = /** @class */ (function () {
415
385
  this._valuesEnd = {};
416
386
  this._valuesStartRepeat = {};
417
387
  this._duration = 1000;
388
+ this._isDynamic = false;
418
389
  this._initialRepeat = 0;
419
390
  this._repeat = 0;
420
391
  this._yoyo = false;
@@ -430,6 +401,7 @@ var Tween = /** @class */ (function () {
430
401
  this._onEveryStartCallbackFired = false;
431
402
  this._id = Sequence.nextId();
432
403
  this._isChainStopped = false;
404
+ this._propertiesAreSetUp = false;
433
405
  this._goToEnd = false;
434
406
  }
435
407
  Tween.prototype.getId = function () {
@@ -441,24 +413,27 @@ var Tween = /** @class */ (function () {
441
413
  Tween.prototype.isPaused = function () {
442
414
  return this._isPaused;
443
415
  };
444
- Tween.prototype.to = function (properties, duration) {
445
- // TODO? restore this, then update the 07_dynamic_to example to set fox
446
- // tween's to on each update. That way the behavior is opt-in (there's
447
- // currently no opt-out).
448
- // for (const prop in properties) this._valuesEnd[prop] = properties[prop]
449
- this._valuesEnd = Object.create(properties);
450
- if (duration !== undefined) {
451
- this._duration = duration;
452
- }
416
+ Tween.prototype.to = function (target, duration) {
417
+ if (duration === void 0) { duration = 1000; }
418
+ if (this._isPlaying)
419
+ throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.');
420
+ this._valuesEnd = target;
421
+ this._propertiesAreSetUp = false;
422
+ this._duration = duration;
453
423
  return this;
454
424
  };
455
- Tween.prototype.duration = function (d) {
456
- if (d === void 0) { d = 1000; }
457
- this._duration = d;
425
+ Tween.prototype.duration = function (duration) {
426
+ if (duration === void 0) { duration = 1000; }
427
+ this._duration = duration;
428
+ return this;
429
+ };
430
+ Tween.prototype.dynamic = function (dynamic) {
431
+ if (dynamic === void 0) { dynamic = false; }
432
+ this._isDynamic = dynamic;
458
433
  return this;
459
434
  };
460
435
  Tween.prototype.start = function (time, overrideStartingValues) {
461
- if (time === void 0) { time = now$1(); }
436
+ if (time === void 0) { time = now(); }
462
437
  if (overrideStartingValues === void 0) { overrideStartingValues = false; }
463
438
  if (this._isPlaying) {
464
439
  return this;
@@ -482,7 +457,17 @@ var Tween = /** @class */ (function () {
482
457
  this._isChainStopped = false;
483
458
  this._startTime = time;
484
459
  this._startTime += this._delayTime;
485
- this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues);
460
+ if (!this._propertiesAreSetUp || overrideStartingValues) {
461
+ this._propertiesAreSetUp = true;
462
+ // If dynamic is not enabled, clone the end values instead of using the passed-in end values.
463
+ if (!this._isDynamic) {
464
+ var tmp = {};
465
+ for (var prop in this._valuesEnd)
466
+ tmp[prop] = this._valuesEnd[prop];
467
+ this._valuesEnd = tmp;
468
+ }
469
+ this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues);
470
+ }
486
471
  return this;
487
472
  };
488
473
  Tween.prototype.startFromCurrentValues = function (time) {
@@ -505,26 +490,42 @@ var Tween = /** @class */ (function () {
505
490
  if (endValues.length === 0) {
506
491
  continue;
507
492
  }
508
- // handle an array of relative values
509
- endValues = endValues.map(this._handleRelativeValue.bind(this, startValue));
510
- // Create a local copy of the Array with the start value at the front
511
- if (_valuesStart[property] === undefined) {
512
- _valuesEnd[property] = [startValue].concat(endValues);
493
+ // Handle an array of relative values.
494
+ // Creates a local copy of the Array with the start value at the front
495
+ var temp = [startValue];
496
+ for (var i = 0, l = endValues.length; i < l; i += 1) {
497
+ var value = this._handleRelativeValue(startValue, endValues[i]);
498
+ if (isNaN(value)) {
499
+ isInterpolationList = false;
500
+ console.warn('Found invalid interpolation list. Skipping.');
501
+ break;
502
+ }
503
+ temp.push(value);
504
+ }
505
+ if (isInterpolationList) {
506
+ // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp.
507
+ _valuesEnd[property] = temp;
508
+ // }
513
509
  }
514
510
  }
515
511
  // handle the deepness of the values
516
512
  if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) {
517
513
  _valuesStart[property] = startValueIsArray ? [] : {};
518
- // eslint-disable-next-line
519
- for (var prop in startValue) {
520
- // eslint-disable-next-line
521
- // @ts-ignore FIXME?
522
- _valuesStart[property][prop] = startValue[prop];
514
+ var nestedObject = startValue;
515
+ for (var prop in nestedObject) {
516
+ _valuesStart[property][prop] = nestedObject[prop];
523
517
  }
524
- _valuesStartRepeat[property] = startValueIsArray ? [] : {}; // TODO? repeat nested values? And yoyo? And array values?
525
- // eslint-disable-next-line
526
- // @ts-ignore FIXME?
527
- this._setupProperties(startValue, _valuesStart[property], _valuesEnd[property], _valuesStartRepeat[property], overrideStartingValues);
518
+ // TODO? repeat nested values? And yoyo? And array values?
519
+ _valuesStartRepeat[property] = startValueIsArray ? [] : {};
520
+ var endValues = _valuesEnd[property];
521
+ // If dynamic is not enabled, clone the end values instead of using the passed-in end values.
522
+ if (!this._isDynamic) {
523
+ var tmp = {};
524
+ for (var prop in endValues)
525
+ tmp[prop] = endValues[prop];
526
+ _valuesEnd[property] = endValues = tmp;
527
+ }
528
+ this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues);
528
529
  }
529
530
  else {
530
531
  // Save the starting value, but only once unless override is requested.
@@ -570,7 +571,7 @@ var Tween = /** @class */ (function () {
570
571
  return this;
571
572
  };
572
573
  Tween.prototype.pause = function (time) {
573
- if (time === void 0) { time = now$1(); }
574
+ if (time === void 0) { time = now(); }
574
575
  if (this._isPaused || !this._isPlaying) {
575
576
  return this;
576
577
  }
@@ -581,7 +582,7 @@ var Tween = /** @class */ (function () {
581
582
  return this;
582
583
  };
583
584
  Tween.prototype.resume = function (time) {
584
- if (time === void 0) { time = now$1(); }
585
+ if (time === void 0) { time = now(); }
585
586
  if (!this._isPaused || !this._isPlaying) {
586
587
  return this;
587
588
  }
@@ -672,7 +673,7 @@ var Tween = /** @class */ (function () {
672
673
  * it is still playing, just paused).
673
674
  */
674
675
  Tween.prototype.update = function (time, autoStart) {
675
- if (time === void 0) { time = now$1(); }
676
+ if (time === void 0) { time = now(); }
676
677
  if (autoStart === void 0) { autoStart = true; }
677
678
  if (this._isPaused)
678
679
  return true;
@@ -795,9 +796,7 @@ var Tween = /** @class */ (function () {
795
796
  if (end.charAt(0) === '+' || end.charAt(0) === '-') {
796
797
  return start + parseFloat(end);
797
798
  }
798
- else {
799
- return parseFloat(end);
800
- }
799
+ return parseFloat(end);
801
800
  };
802
801
  Tween.prototype._swapEndStartRepeatValues = function (property) {
803
802
  var tmp = this._valuesStartRepeat[property];
@@ -813,7 +812,7 @@ var Tween = /** @class */ (function () {
813
812
  return Tween;
814
813
  }());
815
814
 
816
- var VERSION = '19.0.0';
815
+ var VERSION = '20.0.1';
817
816
 
818
817
  /**
819
818
  * Tween.js - Licensed under the MIT license
@@ -844,7 +843,7 @@ var exports = {
844
843
  Easing: Easing,
845
844
  Group: Group,
846
845
  Interpolation: Interpolation,
847
- now: now$1,
846
+ now: now,
848
847
  Sequence: Sequence,
849
848
  nextId: nextId,
850
849
  Tween: Tween,
@@ -856,5 +855,4 @@ var exports = {
856
855
  update: update,
857
856
  };
858
857
 
859
- export default exports;
860
- export { Easing, Group, Interpolation, Sequence, Tween, VERSION, add, getAll, nextId, now$1 as now, remove, removeAll, update };
858
+ export { Easing, Group, Interpolation, Sequence, Tween, VERSION, add, exports as default, getAll, nextId, now, remove, removeAll, update };
package/dist/tween.umd.js CHANGED
@@ -2,7 +2,7 @@
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
3
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.TWEEN = {}));
5
- }(this, (function (exports) { 'use strict';
5
+ })(this, (function (exports) { 'use strict';
6
6
 
7
7
  /**
8
8
  * The Ease class provides a collection of easing functions for use with tween.js.
@@ -219,37 +219,7 @@
219
219
  },
220
220
  });
221
221
 
222
- var now;
223
- // Include a performance.now polyfill.
224
- // In node.js, use process.hrtime.
225
- // eslint-disable-next-line
226
- // @ts-ignore
227
- if (typeof self === 'undefined' && typeof process !== 'undefined' && process.hrtime) {
228
- now = function () {
229
- // eslint-disable-next-line
230
- // @ts-ignore
231
- var time = process.hrtime();
232
- // Convert [seconds, nanoseconds] to milliseconds.
233
- return time[0] * 1000 + time[1] / 1000000;
234
- };
235
- }
236
- // In a browser, use self.performance.now if it is available.
237
- else if (typeof self !== 'undefined' && self.performance !== undefined && self.performance.now !== undefined) {
238
- // This must be bound, because directly assigning this function
239
- // leads to an invocation exception in Chrome.
240
- now = self.performance.now.bind(self.performance);
241
- }
242
- // Use Date.now if it is available.
243
- else if (Date.now !== undefined) {
244
- now = Date.now;
245
- }
246
- // Otherwise, use 'new Date().getTime()'.
247
- else {
248
- now = function () {
249
- return new Date().getTime();
250
- };
251
- }
252
- var now$1 = now;
222
+ var now = function () { return performance.now(); };
253
223
 
254
224
  /**
255
225
  * Controlling groups of tweens
@@ -280,7 +250,7 @@
280
250
  delete this._tweensAddedDuringUpdate[tween.getId()];
281
251
  };
282
252
  Group.prototype.update = function (time, preserve) {
283
- if (time === void 0) { time = now$1(); }
253
+ if (time === void 0) { time = now(); }
284
254
  if (preserve === void 0) { preserve = false; }
285
255
  var tweenIds = Object.keys(this._tweens);
286
256
  if (tweenIds.length === 0) {
@@ -421,6 +391,7 @@
421
391
  this._valuesEnd = {};
422
392
  this._valuesStartRepeat = {};
423
393
  this._duration = 1000;
394
+ this._isDynamic = false;
424
395
  this._initialRepeat = 0;
425
396
  this._repeat = 0;
426
397
  this._yoyo = false;
@@ -436,6 +407,7 @@
436
407
  this._onEveryStartCallbackFired = false;
437
408
  this._id = Sequence.nextId();
438
409
  this._isChainStopped = false;
410
+ this._propertiesAreSetUp = false;
439
411
  this._goToEnd = false;
440
412
  }
441
413
  Tween.prototype.getId = function () {
@@ -447,24 +419,27 @@
447
419
  Tween.prototype.isPaused = function () {
448
420
  return this._isPaused;
449
421
  };
450
- Tween.prototype.to = function (properties, duration) {
451
- // TODO? restore this, then update the 07_dynamic_to example to set fox
452
- // tween's to on each update. That way the behavior is opt-in (there's
453
- // currently no opt-out).
454
- // for (const prop in properties) this._valuesEnd[prop] = properties[prop]
455
- this._valuesEnd = Object.create(properties);
456
- if (duration !== undefined) {
457
- this._duration = duration;
458
- }
422
+ Tween.prototype.to = function (target, duration) {
423
+ if (duration === void 0) { duration = 1000; }
424
+ if (this._isPlaying)
425
+ throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.');
426
+ this._valuesEnd = target;
427
+ this._propertiesAreSetUp = false;
428
+ this._duration = duration;
459
429
  return this;
460
430
  };
461
- Tween.prototype.duration = function (d) {
462
- if (d === void 0) { d = 1000; }
463
- this._duration = d;
431
+ Tween.prototype.duration = function (duration) {
432
+ if (duration === void 0) { duration = 1000; }
433
+ this._duration = duration;
434
+ return this;
435
+ };
436
+ Tween.prototype.dynamic = function (dynamic) {
437
+ if (dynamic === void 0) { dynamic = false; }
438
+ this._isDynamic = dynamic;
464
439
  return this;
465
440
  };
466
441
  Tween.prototype.start = function (time, overrideStartingValues) {
467
- if (time === void 0) { time = now$1(); }
442
+ if (time === void 0) { time = now(); }
468
443
  if (overrideStartingValues === void 0) { overrideStartingValues = false; }
469
444
  if (this._isPlaying) {
470
445
  return this;
@@ -488,7 +463,17 @@
488
463
  this._isChainStopped = false;
489
464
  this._startTime = time;
490
465
  this._startTime += this._delayTime;
491
- this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues);
466
+ if (!this._propertiesAreSetUp || overrideStartingValues) {
467
+ this._propertiesAreSetUp = true;
468
+ // If dynamic is not enabled, clone the end values instead of using the passed-in end values.
469
+ if (!this._isDynamic) {
470
+ var tmp = {};
471
+ for (var prop in this._valuesEnd)
472
+ tmp[prop] = this._valuesEnd[prop];
473
+ this._valuesEnd = tmp;
474
+ }
475
+ this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues);
476
+ }
492
477
  return this;
493
478
  };
494
479
  Tween.prototype.startFromCurrentValues = function (time) {
@@ -511,26 +496,42 @@
511
496
  if (endValues.length === 0) {
512
497
  continue;
513
498
  }
514
- // handle an array of relative values
515
- endValues = endValues.map(this._handleRelativeValue.bind(this, startValue));
516
- // Create a local copy of the Array with the start value at the front
517
- if (_valuesStart[property] === undefined) {
518
- _valuesEnd[property] = [startValue].concat(endValues);
499
+ // Handle an array of relative values.
500
+ // Creates a local copy of the Array with the start value at the front
501
+ var temp = [startValue];
502
+ for (var i = 0, l = endValues.length; i < l; i += 1) {
503
+ var value = this._handleRelativeValue(startValue, endValues[i]);
504
+ if (isNaN(value)) {
505
+ isInterpolationList = false;
506
+ console.warn('Found invalid interpolation list. Skipping.');
507
+ break;
508
+ }
509
+ temp.push(value);
510
+ }
511
+ if (isInterpolationList) {
512
+ // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp.
513
+ _valuesEnd[property] = temp;
514
+ // }
519
515
  }
520
516
  }
521
517
  // handle the deepness of the values
522
518
  if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) {
523
519
  _valuesStart[property] = startValueIsArray ? [] : {};
524
- // eslint-disable-next-line
525
- for (var prop in startValue) {
526
- // eslint-disable-next-line
527
- // @ts-ignore FIXME?
528
- _valuesStart[property][prop] = startValue[prop];
520
+ var nestedObject = startValue;
521
+ for (var prop in nestedObject) {
522
+ _valuesStart[property][prop] = nestedObject[prop];
529
523
  }
530
- _valuesStartRepeat[property] = startValueIsArray ? [] : {}; // TODO? repeat nested values? And yoyo? And array values?
531
- // eslint-disable-next-line
532
- // @ts-ignore FIXME?
533
- this._setupProperties(startValue, _valuesStart[property], _valuesEnd[property], _valuesStartRepeat[property], overrideStartingValues);
524
+ // TODO? repeat nested values? And yoyo? And array values?
525
+ _valuesStartRepeat[property] = startValueIsArray ? [] : {};
526
+ var endValues = _valuesEnd[property];
527
+ // If dynamic is not enabled, clone the end values instead of using the passed-in end values.
528
+ if (!this._isDynamic) {
529
+ var tmp = {};
530
+ for (var prop in endValues)
531
+ tmp[prop] = endValues[prop];
532
+ _valuesEnd[property] = endValues = tmp;
533
+ }
534
+ this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues);
534
535
  }
535
536
  else {
536
537
  // Save the starting value, but only once unless override is requested.
@@ -576,7 +577,7 @@
576
577
  return this;
577
578
  };
578
579
  Tween.prototype.pause = function (time) {
579
- if (time === void 0) { time = now$1(); }
580
+ if (time === void 0) { time = now(); }
580
581
  if (this._isPaused || !this._isPlaying) {
581
582
  return this;
582
583
  }
@@ -587,7 +588,7 @@
587
588
  return this;
588
589
  };
589
590
  Tween.prototype.resume = function (time) {
590
- if (time === void 0) { time = now$1(); }
591
+ if (time === void 0) { time = now(); }
591
592
  if (!this._isPaused || !this._isPlaying) {
592
593
  return this;
593
594
  }
@@ -678,7 +679,7 @@
678
679
  * it is still playing, just paused).
679
680
  */
680
681
  Tween.prototype.update = function (time, autoStart) {
681
- if (time === void 0) { time = now$1(); }
682
+ if (time === void 0) { time = now(); }
682
683
  if (autoStart === void 0) { autoStart = true; }
683
684
  if (this._isPaused)
684
685
  return true;
@@ -801,9 +802,7 @@
801
802
  if (end.charAt(0) === '+' || end.charAt(0) === '-') {
802
803
  return start + parseFloat(end);
803
804
  }
804
- else {
805
- return parseFloat(end);
806
- }
805
+ return parseFloat(end);
807
806
  };
808
807
  Tween.prototype._swapEndStartRepeatValues = function (property) {
809
808
  var tmp = this._valuesStartRepeat[property];
@@ -819,7 +818,7 @@
819
818
  return Tween;
820
819
  }());
821
820
 
822
- var VERSION = '19.0.0';
821
+ var VERSION = '20.0.1';
823
822
 
824
823
  /**
825
824
  * Tween.js - Licensed under the MIT license
@@ -850,7 +849,7 @@
850
849
  Easing: Easing,
851
850
  Group: Group,
852
851
  Interpolation: Interpolation,
853
- now: now$1,
852
+ now: now,
854
853
  Sequence: Sequence,
855
854
  nextId: nextId,
856
855
  Tween: Tween,
@@ -872,11 +871,11 @@
872
871
  exports.default = exports$1;
873
872
  exports.getAll = getAll;
874
873
  exports.nextId = nextId;
875
- exports.now = now$1;
874
+ exports.now = now;
876
875
  exports.remove = remove;
877
876
  exports.removeAll = removeAll;
878
877
  exports.update = update;
879
878
 
880
879
  Object.defineProperty(exports, '__esModule', { value: true });
881
880
 
882
- })));
881
+ }));
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@tweenjs/tween.js",
3
3
  "description": "Super simple, fast and easy to use tweening engine which incorporates optimised Robert Penner's equations.",
4
- "version": "19.0.0",
4
+ "version": "20.0.1",
5
+ "type": "module",
5
6
  "main": "dist/tween.cjs.js",
6
7
  "types": "dist/tween.d.ts",
7
8
  "module": "dist/tween.esm.js",
@@ -32,7 +33,7 @@
32
33
  "tsc-watch": "tsc --watch",
33
34
  "examples": "npx serve .",
34
35
  "test": "npm run build && npm run test-lint && npm run test-unit",
35
- "test-unit": "nodeunit test/unit/nodeunitheadless.js",
36
+ "test-unit": "nodeunit test/unit/nodeunitheadless.cjs",
36
37
  "test-lint": "npm run prettier -- --check && eslint 'src/**/*.ts'",
37
38
  "lint": "npm run prettier -- --write && eslint 'src/**/*.ts' --fix",
38
39
  "prettier": "prettier './**/*.{js,ts,md,json,html,css}'",
@@ -45,19 +46,17 @@
45
46
  },
46
47
  "author": "tween.js contributors (https://github.com/tweenjs/tween.js/graphs/contributors)",
47
48
  "devDependencies": {
48
- "@sinonjs/fake-timers": "^6.0.1",
49
- "@types/sinonjs__fake-timers": "^6.0.2",
50
49
  "@typescript-eslint/eslint-plugin": "^3.1.0",
51
50
  "@typescript-eslint/parser": "^3.1.0",
52
51
  "eslint": "^7.1.0",
53
52
  "eslint-config-prettier": "^6.7.0",
54
53
  "eslint-plugin-prettier": "^3.1.1",
55
54
  "nodeunit": "^0.11.3",
56
- "prettier": "^2.0.0",
55
+ "prettier": "2.8.7",
57
56
  "rimraf": "^3.0.0",
58
- "rollup": "^2.0.0",
59
- "rollup-plugin-dts": "1.4.10",
57
+ "rollup": "3.20.7",
58
+ "rollup-plugin-dts": "5.3.0",
60
59
  "tslib": "^1.10.0",
61
- "typescript": "^4.0.0"
60
+ "typescript": "5.0.4"
62
61
  }
63
62
  }