tiny-essentials 1.25.0 → 1.25.2

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.
@@ -0,0 +1,653 @@
1
+ /**
2
+ * Configuration object for the Analog Clock.
3
+ * @typedef {Object} ClockConfig
4
+ * @property {string} bgColor - The background color of the clock face (e.g., '#ffffff', 'rgb(0,0,0)').
5
+ * @property {string} borderColor - The color of the clock's outer border.
6
+ * @property {number} borderWidth - The thickness of the outer border in pixels.
7
+ * @property {string} markColor - The color of the minute/hour tick marks.
8
+ * @property {string} hourHandColor - The color of the hour hand.
9
+ * @property {string} minuteHandColor - The color of the minute hand.
10
+ * @property {string} secondHandColor - The color of the second hand.
11
+ * @property {string} textColor - The color of the numbers on the clock face.
12
+ * @property {string|null} skinUrl - The URL of an image to use as the clock face background. Set to null to use bgColor.
13
+ * @property {boolean} showNumbers - Whether to render the numbers (1-12) on the clock face.
14
+ * @property {boolean} showSeconds - Whether to display the second hand.
15
+ * @property {number} size - The total width and height of the clock in pixels.
16
+ * @property {number} sizeAdjust - Scale factor for the font size of the numbers relative to the clock size.
17
+ * @property {number} padding - Padding in pixels between the clock edge and the tick marks.
18
+ * @property {number} angleDistance - Distance multiplier (0-1) for placing numbers relative to the radius.
19
+ * @property {number} pwH - Hour tick width as a percentage of clock size (0.0 to 1.0).
20
+ * @property {number} phH - Hour tick height as a percentage of clock size (0.0 to 1.0).
21
+ * @property {number} pwM - Minute tick width as a percentage of clock size (0.0 to 1.0).
22
+ * @property {number} phM - Minute tick height as a percentage of clock size (0.0 to 1.0).
23
+ */
24
+ class TinyAnalogClock {
25
+ /** @type {HTMLElement} */
26
+ #element;
27
+ /** @type {HTMLElement} */
28
+ #faceLayer;
29
+ /** @type {HTMLElement} */
30
+ #skinLayer;
31
+ /** @type {ClockConfig} */
32
+ #config;
33
+ /** @type {number|null} */
34
+ #animationFrame = null;
35
+ /**
36
+ * Retrieves the current date and extracts the specific hour, minute, and second components.
37
+ * @returns {{ now: Date, s: number, m: number, h: number }}
38
+ * @private
39
+ */
40
+ _getDate() {
41
+ const now = new Date();
42
+ const s = now.getSeconds();
43
+ const m = now.getMinutes();
44
+ const h = now.getHours();
45
+ return { now, s, m, h };
46
+ }
47
+ /**
48
+ * Creates an instance of TinyAnalogClock.
49
+ * Initializes the DOM structure and starts the animation loop.
50
+ * @param {Partial<ClockConfig>} [options] - Optional configuration overrides.
51
+ */
52
+ constructor(options = {}) {
53
+ this.#config = {
54
+ bgColor: '#f0f0f0',
55
+ borderColor: '#333',
56
+ borderWidth: 8,
57
+ markColor: '#333',
58
+ hourHandColor: '#000',
59
+ minuteHandColor: '#444',
60
+ secondHandColor: '#d81c1c',
61
+ textColor: '#000',
62
+ skinUrl: null,
63
+ showNumbers: true,
64
+ showSeconds: true,
65
+ size: 800,
66
+ sizeAdjust: 0.04,
67
+ padding: 45,
68
+ angleDistance: 0.95,
69
+ pwH: 0.008,
70
+ phH: 0.08,
71
+ pwM: 0.005,
72
+ phM: 0.03,
73
+ ...options,
74
+ };
75
+ this.#element = document.createElement('div');
76
+ this.#element.className = 'analog-clock-container';
77
+ // Base styles for the container
78
+ this.#element.style.position = 'relative';
79
+ this.#element.style.borderRadius = '50%';
80
+ this.#element.style.overflow = 'hidden';
81
+ this.#element.style.boxSizing = 'border-box';
82
+ // 1. Skin Layer (Background Image)
83
+ this.#skinLayer = document.createElement('div');
84
+ this.#skinLayer.style.position = 'absolute';
85
+ this.#skinLayer.style.inset = '0';
86
+ this.#skinLayer.style.backgroundSize = 'cover';
87
+ this.#skinLayer.style.backgroundPosition = 'center';
88
+ this.#skinLayer.style.zIndex = '0';
89
+ // 2. Face Layer (Ticks and Numbers)
90
+ this.#faceLayer = document.createElement('div');
91
+ this.#faceLayer.style.position = 'absolute';
92
+ this.#faceLayer.style.inset = '0';
93
+ this.#faceLayer.style.zIndex = '1';
94
+ this.#faceLayer.style.pointerEvents = 'none';
95
+ // 3. Hands Layer (Hour, Minute, Second hands)
96
+ const handsLayer = document.createElement('div');
97
+ handsLayer.style.position = 'absolute';
98
+ handsLayer.style.inset = '0';
99
+ handsLayer.style.zIndex = '2';
100
+ handsLayer.style.pointerEvents = 'none';
101
+ handsLayer.innerHTML = `
102
+ <div class="hand hour-hand"></div>
103
+ <div class="hand minute-hand"></div>
104
+ <div class="hand second-hand"></div>
105
+ <div class="center-pin"></div>
106
+ `;
107
+ this.#element.appendChild(this.#skinLayer);
108
+ this.#element.appendChild(this.#faceLayer);
109
+ this.#element.appendChild(handsLayer);
110
+ // Inject Styles dynamically to keep the class self-contained
111
+ const style = document.createElement('style');
112
+ style.textContent = `
113
+ .analog-clock-container .hand {
114
+ position: absolute;
115
+ bottom: 50%;
116
+ left: 50%;
117
+ transform-origin: bottom center;
118
+ border-radius: 4px;
119
+ transform: translateX(-50%) rotate(0deg);
120
+ z-index: 5;
121
+ }
122
+ .analog-clock-container .center-pin {
123
+ position: absolute;
124
+ top: 50%; left: 50%;
125
+ width: 12px; height: 12px;
126
+ background: #333;
127
+ border-radius: 50%;
128
+ transform: translate(-50%, -50%);
129
+ z-index: 10;
130
+ }
131
+ .analog-clock-container .clock-mark {
132
+ position: absolute;
133
+ top: 50%; left: 50%;
134
+ background: currentColor;
135
+ transform-origin: center center;
136
+ }
137
+ .analog-clock-container .clock-number {
138
+ position: absolute;
139
+ top: 50%; left: 50%;
140
+ transform: translate(-50%, -50%);
141
+ font-family: sans-serif;
142
+ font-weight: bold;
143
+ text-align: center;
144
+ line-height: 1;
145
+ }
146
+ `;
147
+ this.#element.appendChild(style);
148
+ this._applyConfig();
149
+ this._renderFace();
150
+ this._startTicker();
151
+ }
152
+ /**
153
+ * Applies the current configuration to the DOM elements (colors, sizes, visibility).
154
+ * @private
155
+ */
156
+ _applyConfig() {
157
+ const s = this.#element.style;
158
+ const c = this.#config;
159
+ s.width = `${c.size}px`;
160
+ s.height = `${c.size}px`;
161
+ s.border = `${c.borderWidth}px solid ${c.borderColor}`;
162
+ s.backgroundColor = c.bgColor;
163
+ if (c.skinUrl) {
164
+ this.#skinLayer.style.backgroundImage = `url(${c.skinUrl})`;
165
+ this.#skinLayer.style.display = 'block';
166
+ }
167
+ else {
168
+ this.#skinLayer.style.display = 'none';
169
+ }
170
+ /**
171
+ * Helper to safely query elements within the clock.
172
+ * @param {string} sel
173
+ * @returns {HTMLDivElement}
174
+ */
175
+ const q = (sel) => {
176
+ const result = this.#element.querySelector(sel);
177
+ if (!(result instanceof HTMLDivElement))
178
+ throw new Error(`TinyAnalogClock: Element ${sel} not found.`);
179
+ return result;
180
+ };
181
+ // Hour Hand
182
+ const hHand = q('.hour-hand');
183
+ hHand.style.backgroundColor = c.hourHandColor;
184
+ hHand.style.width = `${c.size * 0.025}px`;
185
+ hHand.style.height = `${c.size * 0.25}px`;
186
+ hHand.style.marginLeft = `${(c.size * 0.025) / -2}px`; // Center alignment fix
187
+ hHand.style.bottom = '50%';
188
+ hHand.style.left = '50%';
189
+ hHand.style.transformOrigin = 'bottom center';
190
+ // Minute Hand
191
+ const mHand = q('.minute-hand');
192
+ mHand.style.backgroundColor = c.minuteHandColor;
193
+ mHand.style.width = `${c.size * 0.015}px`;
194
+ mHand.style.height = `${c.size * 0.35}px`;
195
+ mHand.style.marginLeft = `${(c.size * 0.015) / -2}px`;
196
+ // Second Hand
197
+ const sHand = q('.second-hand');
198
+ if (c.showSeconds) {
199
+ sHand.style.display = 'block';
200
+ sHand.style.backgroundColor = c.secondHandColor;
201
+ sHand.style.width = `${c.size * 0.005}px`;
202
+ sHand.style.height = `${c.size * 0.4}px`;
203
+ sHand.style.marginLeft = `${(c.size * 0.005) / -2}px`;
204
+ }
205
+ else {
206
+ sHand.style.display = 'none';
207
+ }
208
+ q('.center-pin').style.background = c.borderColor;
209
+ }
210
+ /**
211
+ * Renders the clock face, including tick marks and numbers, based on the current size and config.
212
+ * Uses trigonometry to position elements perfectly from the center.
213
+ * @private
214
+ */
215
+ _renderFace() {
216
+ this.#faceLayer.innerHTML = '';
217
+ const radius = this.#config.size / 2 - this.#config.borderWidth;
218
+ // 1. Render Ticks (Lines)
219
+ for (let i = 0; i < 60; i++) {
220
+ const isHour = i % 5 === 0;
221
+ const el = document.createElement('div');
222
+ el.className = 'clock-mark';
223
+ const w = isHour
224
+ ? this.#config.size * this.#config.pwH
225
+ : this.#config.size * this.#config.pwM;
226
+ const h = isHour
227
+ ? this.#config.size * this.#config.phH
228
+ : this.#config.size * this.#config.phM;
229
+ const color = this.#config.markColor;
230
+ el.style.width = `${w}px`;
231
+ el.style.height = `${h}px`;
232
+ el.style.color = color;
233
+ // Distance from center to the TICK's center
234
+ // We want the tick to be close to the edge.
235
+ // Distance = radius - padding - (half of tick height)
236
+ const distanceFromCenter = radius - this.#config.padding - h / 2;
237
+ // Logic: Start at center (50% 50%) -> Rotate -> Push Outwards
238
+ el.style.transform = `translate(-50%, -50%) rotate(${i * 6}deg) translate(0, -${distanceFromCenter}px)`;
239
+ this.#faceLayer.appendChild(el);
240
+ }
241
+ // 2. Render Numbers
242
+ if (this.#config.showNumbers) {
243
+ for (let i = 1; i <= 12; i++) {
244
+ const angle = i * 30 * (Math.PI / 180);
245
+ // Adjust radius for text position (80% of total radius)
246
+ const dist = radius * this.#config.angleDistance;
247
+ const x = radius + Math.sin(angle) * dist;
248
+ const y = radius - Math.cos(angle) * dist;
249
+ const num = document.createElement('div');
250
+ num.className = 'clock-number';
251
+ num.innerText = i.toString();
252
+ num.style.left = `${x}px`;
253
+ num.style.top = `${y}px`;
254
+ num.style.color = this.#config.textColor;
255
+ num.style.fontSize = `${this.#config.size * this.#config.sizeAdjust}px`;
256
+ this.#faceLayer.appendChild(num);
257
+ }
258
+ }
259
+ }
260
+ /**
261
+ * Starts the RequestAnimationFrame loop to update hand positions.
262
+ * @private
263
+ */
264
+ _startTicker() {
265
+ if (this.#animationFrame)
266
+ return;
267
+ const update = () => {
268
+ const { s, m, h } = this._getDate();
269
+ // Calculate degrees
270
+ const sDeg = s * 6;
271
+ const mDeg = m * 6 + s * 0.1;
272
+ const hDeg = (h % 12) * 30 + m * 0.5;
273
+ /**
274
+ * @param {string} sel
275
+ * @returns {HTMLDivElement}
276
+ */
277
+ const q = (sel) => {
278
+ const result = this.#element.querySelector(sel);
279
+ if (!(result instanceof HTMLDivElement))
280
+ throw new Error(`${sel} not found.`);
281
+ return result;
282
+ };
283
+ if (this.#element.querySelector('.second-hand')) {
284
+ q('.second-hand').style.transform = `rotate(${sDeg}deg)`;
285
+ }
286
+ q('.minute-hand').style.transform = `rotate(${mDeg}deg)`;
287
+ q('.hour-hand').style.transform = `rotate(${hDeg}deg)`;
288
+ this.#animationFrame = requestAnimationFrame(update);
289
+ };
290
+ update();
291
+ }
292
+ // ==========================================
293
+ // GETTERS & SETTERS (Full Access)
294
+ // ==========================================
295
+ /**
296
+ * Gets the main HTML element of the clock.
297
+ * @returns {HTMLElement} The clock container.
298
+ */
299
+ get element() {
300
+ return this.#element;
301
+ }
302
+ /**
303
+ * Sets the size of the clock.
304
+ * @param {number} value - The new size in pixels. Must be a positive number.
305
+ * @throws {Error} If value is not a positive number.
306
+ */
307
+ set size(value) {
308
+ if (typeof value !== 'number' || value <= 0 || isNaN(value)) {
309
+ throw new Error(`TinyAnalogClock: 'size' must be a positive number. Received: ${value}`);
310
+ }
311
+ this.#config.size = value;
312
+ this._applyConfig();
313
+ this._renderFace();
314
+ }
315
+ /** * Gets the clock size in pixels.
316
+ * @returns {number}
317
+ */
318
+ get size() {
319
+ return this.#config.size;
320
+ }
321
+ /**
322
+ * Sets the background image (skin) of the clock.
323
+ * @param {string|null} url - The URL string of the image, or null to remove the skin.
324
+ * @throws {Error} If value is not a string or null.
325
+ */
326
+ set skinUrl(url) {
327
+ if (url !== null && typeof url !== 'string') {
328
+ throw new Error(`TinyAnalogClock: 'skin' must be a URL string or null. Received type: ${typeof url}`);
329
+ }
330
+ this.#config.skinUrl = url;
331
+ this._applyConfig();
332
+ }
333
+ /**
334
+ * Gets the skin URL.
335
+ * @returns {string|null}
336
+ */
337
+ get skinUrl() {
338
+ return this.#config.skinUrl;
339
+ }
340
+ /**
341
+ * Sets the primary border color.
342
+ * @param {string} color - A valid CSS color string.
343
+ * @throws {Error} If value is not a non-empty string.
344
+ */
345
+ set borderColor(color) {
346
+ if (typeof color !== 'string' || color.trim() === '') {
347
+ throw new Error(`TinyAnalogClock: 'borderColor' must be a non-empty string. Received: ${color}`);
348
+ }
349
+ this.#config.borderColor = color;
350
+ this._applyConfig();
351
+ this._renderFace();
352
+ }
353
+ /**
354
+ * Gets the border color.
355
+ * @returns {string}
356
+ */
357
+ get borderColor() {
358
+ return this.#config.borderColor;
359
+ }
360
+ /**
361
+ * Sets the primary marks color.
362
+ * @param {string} color - A valid CSS color string.
363
+ * @throws {Error} If value is not a non-empty string.
364
+ */
365
+ set markColor(color) {
366
+ if (typeof color !== 'string' || color.trim() === '') {
367
+ throw new Error(`TinyAnalogClock: 'markColor' must be a non-empty string. Received: ${color}`);
368
+ }
369
+ this.#config.markColor = color;
370
+ this._applyConfig();
371
+ this._renderFace();
372
+ }
373
+ /**
374
+ * Gets the tick marks color.
375
+ * @returns {string}
376
+ */
377
+ get markColor() {
378
+ return this.#config.markColor;
379
+ }
380
+ /**
381
+ * Sets the primary text color.
382
+ * @param {string} color - A valid CSS color string.
383
+ * @throws {Error} If value is not a non-empty string.
384
+ */
385
+ set textColor(color) {
386
+ if (typeof color !== 'string' || color.trim() === '') {
387
+ throw new Error(`TinyAnalogClock: 'textColor' must be a non-empty string. Received: ${color}`);
388
+ }
389
+ this.#config.textColor = color;
390
+ this._applyConfig();
391
+ this._renderFace();
392
+ }
393
+ /**
394
+ * Gets the text color.
395
+ * @returns {string}
396
+ */
397
+ get textColor() {
398
+ return this.#config.textColor;
399
+ }
400
+ /**
401
+ * Toggles the visibility of numbers on the clock face.
402
+ * @param {boolean} value - True to show numbers, false to hide.
403
+ * @throws {Error} If value is not a boolean.
404
+ */
405
+ set showNumbers(value) {
406
+ if (typeof value !== 'boolean') {
407
+ throw new Error(`TinyAnalogClock: 'showNumbers' must be a boolean. Received type: ${typeof value}`);
408
+ }
409
+ this.#config.showNumbers = value;
410
+ this._renderFace();
411
+ }
412
+ /**
413
+ * Gets numbers visibility.
414
+ * @returns {boolean}
415
+ */
416
+ get showNumbers() {
417
+ return this.#config.showNumbers;
418
+ }
419
+ /**
420
+ * Sets the background color.
421
+ * @param {string} value
422
+ */
423
+ set bgColor(value) {
424
+ if (typeof value !== 'string' || !value)
425
+ throw new Error("TinyAnalogClock: 'bgColor' must be a non-empty string.");
426
+ this.#config.bgColor = value;
427
+ this._applyConfig();
428
+ }
429
+ /**
430
+ * Gets the background color.
431
+ * @returns {string}
432
+ */
433
+ get bgColor() {
434
+ return this.#config.bgColor;
435
+ }
436
+ /**
437
+ * Sets the border thickness in pixels.
438
+ * @param {number} value
439
+ */
440
+ set borderWidth(value) {
441
+ if (typeof value !== 'number' || value < 0)
442
+ throw new Error("TinyAnalogClock: 'borderWidth' must be a non-negative number.");
443
+ this.#config.borderWidth = value;
444
+ this._applyConfig();
445
+ this._renderFace(); // Radius changes, so we must re-render face
446
+ }
447
+ /**
448
+ * Gets the border thickness.
449
+ * @returns {number}
450
+ */
451
+ get borderWidth() {
452
+ return this.#config.borderWidth;
453
+ }
454
+ /**
455
+ * Sets the hour hand color.
456
+ * @param {string} value
457
+ */
458
+ set hourHandColor(value) {
459
+ if (typeof value !== 'string' || !value)
460
+ throw new Error("TinyAnalogClock: 'hourHandColor' must be a non-empty string.");
461
+ this.#config.hourHandColor = value;
462
+ this._applyConfig();
463
+ }
464
+ /**
465
+ * Gets the hour hand color.
466
+ * @returns {string}
467
+ */
468
+ get hourHandColor() {
469
+ return this.#config.hourHandColor;
470
+ }
471
+ /**
472
+ * Sets the minute hand color.
473
+ * @param {string} value
474
+ */
475
+ set minuteHandColor(value) {
476
+ if (typeof value !== 'string' || !value)
477
+ throw new Error("TinyAnalogClock: 'minuteHandColor' must be a non-empty string.");
478
+ this.#config.minuteHandColor = value;
479
+ this._applyConfig();
480
+ }
481
+ /**
482
+ * Gets the minute hand color.
483
+ * @returns {string}
484
+ */
485
+ get minuteHandColor() {
486
+ return this.#config.minuteHandColor;
487
+ }
488
+ /**
489
+ * Sets the second hand color.
490
+ * @param {string} value
491
+ */
492
+ set secondHandColor(value) {
493
+ if (typeof value !== 'string' || !value)
494
+ throw new Error("TinyAnalogClock: 'secondHandColor' must be a non-empty string.");
495
+ this.#config.secondHandColor = value;
496
+ this._applyConfig();
497
+ }
498
+ /**
499
+ * Gets the second hand color.
500
+ * @returns {string}
501
+ */
502
+ get secondHandColor() {
503
+ return this.#config.secondHandColor;
504
+ }
505
+ /**
506
+ * Toggle second hand visibility.
507
+ * @param {boolean} value
508
+ */
509
+ set showSeconds(value) {
510
+ if (typeof value !== 'boolean')
511
+ throw new Error("TinyAnalogClock: 'showSeconds' must be a boolean.");
512
+ this.#config.showSeconds = value;
513
+ this._applyConfig();
514
+ }
515
+ /**
516
+ * Gets second hand visibility.
517
+ * @returns {boolean}
518
+ */
519
+ get showSeconds() {
520
+ return this.#config.showSeconds;
521
+ }
522
+ /**
523
+ * Sets the font size adjustment factor.
524
+ * @param {number} value - Positive number.
525
+ */
526
+ set sizeAdjust(value) {
527
+ if (typeof value !== 'number' || value <= 0)
528
+ throw new Error("TinyAnalogClock: 'sizeAdjust' must be a positive number.");
529
+ this.#config.sizeAdjust = value;
530
+ this._renderFace();
531
+ }
532
+ /**
533
+ * Gets the font size adjustment factor.
534
+ * @returns {number}
535
+ */
536
+ get sizeAdjust() {
537
+ return this.#config.sizeAdjust;
538
+ }
539
+ /**
540
+ * Sets the padding from edge to ticks.
541
+ * @param {number} value - Non-negative number in pixels.
542
+ */
543
+ set padding(value) {
544
+ if (typeof value !== 'number' || value < 0)
545
+ throw new Error("TinyAnalogClock: 'padding' must be a non-negative number.");
546
+ this.#config.padding = value;
547
+ this._renderFace();
548
+ }
549
+ /**
550
+ * Gets the padding.
551
+ * @returns {number}
552
+ */
553
+ get padding() {
554
+ return this.#config.padding;
555
+ }
556
+ /**
557
+ * Sets the angle distance multiplier for numbers placement.
558
+ * @param {number} value - Positive number.
559
+ */
560
+ set angleDistance(value) {
561
+ if (typeof value !== 'number' || value <= 0)
562
+ throw new Error("TinyAnalogClock: 'angleDistance' must be a positive number.");
563
+ this.#config.angleDistance = value;
564
+ this._renderFace();
565
+ }
566
+ /**
567
+ * Gets the angle distance multiplier.
568
+ * @returns {number}
569
+ */
570
+ get angleDistance() {
571
+ return this.#config.angleDistance;
572
+ }
573
+ /**
574
+ * Sets the Hour tick width percentage.
575
+ * @param {number} value - Positive number.
576
+ */
577
+ set pwH(value) {
578
+ if (typeof value !== 'number' || value <= 0)
579
+ throw new Error("TinyAnalogClock: 'pwH' must be a positive number.");
580
+ this.#config.pwH = value;
581
+ this._renderFace();
582
+ }
583
+ /**
584
+ * Gets the Hour tick width percentage.
585
+ * @returns {number}
586
+ */
587
+ get pwH() {
588
+ return this.#config.pwH;
589
+ }
590
+ /**
591
+ * Sets the Hour tick height percentage.
592
+ * @param {number} value - Positive number.
593
+ */
594
+ set phH(value) {
595
+ if (typeof value !== 'number' || value <= 0)
596
+ throw new Error("TinyAnalogClock: 'phH' must be a positive number.");
597
+ this.#config.phH = value;
598
+ this._renderFace();
599
+ }
600
+ /**
601
+ * Gets the Hour tick height percentage.
602
+ * @returns {number}
603
+ */
604
+ get phH() {
605
+ return this.#config.phH;
606
+ }
607
+ /**
608
+ * Sets the Minute tick width percentage.
609
+ * @param {number} value - Positive number.
610
+ */
611
+ set pwM(value) {
612
+ if (typeof value !== 'number' || value <= 0)
613
+ throw new Error("TinyAnalogClock: 'pwM' must be a positive number.");
614
+ this.#config.pwM = value;
615
+ this._renderFace();
616
+ }
617
+ /**
618
+ * Gets the Minute tick width percentage.
619
+ * @returns {number}
620
+ */
621
+ get pwM() {
622
+ return this.#config.pwM;
623
+ }
624
+ /**
625
+ * Sets the Minute tick height percentage.
626
+ * @param {number} value - Positive number.
627
+ */
628
+ set phM(value) {
629
+ if (typeof value !== 'number' || value <= 0)
630
+ throw new Error("TinyAnalogClock: 'phM' must be a positive number.");
631
+ this.#config.phM = value;
632
+ this._renderFace();
633
+ }
634
+ /**
635
+ * Gets the Minute tick height percentage.
636
+ * @returns {number}
637
+ */
638
+ get phM() {
639
+ return this.#config.phM;
640
+ }
641
+ /**
642
+ * Destroys the clock instance, stops animations, and removes the element from DOM.
643
+ * @returns {void}
644
+ */
645
+ destroy() {
646
+ if (this.#animationFrame) {
647
+ cancelAnimationFrame(this.#animationFrame);
648
+ this.#animationFrame = null;
649
+ }
650
+ this.#element.remove();
651
+ }
652
+ }
653
+ export default TinyAnalogClock;