@jjlmoya/utils-chrono 1.9.0 → 1.10.0
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/package.json +1 -1
- package/src/category/index.ts +2 -0
- package/src/entries.ts +4 -1
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/sidereal-time-tracker/bibliography.astro +16 -0
- package/src/tool/sidereal-time-tracker/bibliography.ts +16 -0
- package/src/tool/sidereal-time-tracker/client.ts +278 -0
- package/src/tool/sidereal-time-tracker/component.astro +15 -0
- package/src/tool/sidereal-time-tracker/components/SiderealPanel.astro +197 -0
- package/src/tool/sidereal-time-tracker/entry.ts +51 -0
- package/src/tool/sidereal-time-tracker/helpers.ts +80 -0
- package/src/tool/sidereal-time-tracker/i18n/de.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/en.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/es.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/fr.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/id.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/it.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/ja.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/ko.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/nl.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/pl.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/pt.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/ru.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/sv.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/tr.ts +93 -0
- package/src/tool/sidereal-time-tracker/i18n/zh.ts +93 -0
- package/src/tool/sidereal-time-tracker/index.ts +11 -0
- package/src/tool/sidereal-time-tracker/seo.astro +16 -0
- package/src/tool/sidereal-time-tracker/sidereal-time-tracker.css +257 -0
- package/src/tools.ts +2 -0
package/package.json
CHANGED
package/src/category/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { tachymeterCalculator } from '../tool/tachymeter-calculator/entry';
|
|
|
14
14
|
import { serviceIntervalTracker } from '../tool/service-interval-tracker/entry';
|
|
15
15
|
import { strapLengthCalculator } from '../tool/strap-length-calculator/entry';
|
|
16
16
|
import { telemeterCalculator } from '../tool/telemeter-calculator/entry';
|
|
17
|
+
import { siderealTimeTracker } from '../tool/sidereal-time-tracker/entry';
|
|
17
18
|
|
|
18
19
|
export const chronoCategory: ChronoCategoryEntry = {
|
|
19
20
|
icon: 'mdi:clock-outline',
|
|
@@ -33,6 +34,7 @@ export const chronoCategory: ChronoCategoryEntry = {
|
|
|
33
34
|
serviceIntervalTracker,
|
|
34
35
|
strapLengthCalculator,
|
|
35
36
|
telemeterCalculator,
|
|
37
|
+
siderealTimeTracker,
|
|
36
38
|
],
|
|
37
39
|
i18n: {
|
|
38
40
|
de: () => import('./i18n/de').then((m) => m.content),
|
package/src/entries.ts
CHANGED
|
@@ -30,6 +30,8 @@ export { strapLengthCalculator } from './tool/strap-length-calculator/entry';
|
|
|
30
30
|
export type { StrapLengthCalculatorUI, StrapLengthCalculatorLocaleContent } from './tool/strap-length-calculator/entry';
|
|
31
31
|
export { telemeterCalculator } from './tool/telemeter-calculator/entry';
|
|
32
32
|
export type { TelemeterCalculatorUI, TelemeterCalculatorLocaleContent } from './tool/telemeter-calculator/entry';
|
|
33
|
+
export { siderealTimeTracker } from './tool/sidereal-time-tracker/entry';
|
|
34
|
+
export type { SiderealTimeTrackerUI, SiderealTimeTrackerLocaleContent } from './tool/sidereal-time-tracker/entry';
|
|
33
35
|
export { chronoCategory } from './category';
|
|
34
36
|
|
|
35
37
|
import { watchAccuracyTracker } from './tool/watch-accuracy-tracker/entry';
|
|
@@ -48,6 +50,7 @@ import { tachymeterCalculator } from './tool/tachymeter-calculator/entry';
|
|
|
48
50
|
import { serviceIntervalTracker } from './tool/service-interval-tracker/entry';
|
|
49
51
|
import { strapLengthCalculator } from './tool/strap-length-calculator/entry';
|
|
50
52
|
import { telemeterCalculator } from './tool/telemeter-calculator/entry';
|
|
53
|
+
import { siderealTimeTracker } from './tool/sidereal-time-tracker/entry';
|
|
51
54
|
|
|
52
|
-
export const ALL_ENTRIES = [watchAccuracyTracker, wristPresenceCalculator, demagnetizingTimer, watchSavingsPlanner, crownReferenceGuide, powerReserveEstimator, beatRateConverter, waterResistanceConverter, strapTaperCalculator, watchSizeComparator, lumeColorSimulator, moonPhaseVisualizer, tachymeterCalculator, serviceIntervalTracker, strapLengthCalculator, telemeterCalculator];
|
|
55
|
+
export const ALL_ENTRIES = [watchAccuracyTracker, wristPresenceCalculator, demagnetizingTimer, watchSavingsPlanner, crownReferenceGuide, powerReserveEstimator, beatRateConverter, waterResistanceConverter, strapTaperCalculator, watchSizeComparator, lumeColorSimulator, moonPhaseVisualizer, tachymeterCalculator, serviceIntervalTracker, strapLengthCalculator, telemeterCalculator, siderealTimeTracker];
|
|
53
56
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { siderealTimeTracker } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props as Props;
|
|
11
|
+
const loader = siderealTimeTracker.i18n[locale] || siderealTimeTracker.i18n.en;
|
|
12
|
+
const content = await loader?.();
|
|
13
|
+
if (!content) return null;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'Sidereal Time - Wikipedia',
|
|
6
|
+
url: 'https://en.wikipedia.org/wiki/Sidereal_time',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Greenwich Mean Sidereal Time - US Naval Observatory',
|
|
10
|
+
url: 'https://aa.usno.navy.mil/data/siderealtime',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'Patek Philippe Celestial - Astronomical Watches complication',
|
|
14
|
+
url: 'https://www.patek.com/en/service/taking-care-of-your-watch/settings/celestial',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { getJulianDate, getLST, formatHours } from './helpers';
|
|
2
|
+
|
|
3
|
+
let simulatedTime = Date.now();
|
|
4
|
+
let lastFrameTime = Date.now();
|
|
5
|
+
let speedMultiplier = 1;
|
|
6
|
+
let longitude = 0;
|
|
7
|
+
let longitudeFormat: 'decimal' | 'dms' = 'decimal';
|
|
8
|
+
let hemisphere: 'N' | 'S' = 'N';
|
|
9
|
+
let audioEnabled = false;
|
|
10
|
+
let audioCtx: AudioContext | null = null;
|
|
11
|
+
let lastTickSecond = -1;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const storedLon = localStorage.getItem('sid-longitude');
|
|
15
|
+
if (storedLon) longitude = parseFloat(storedLon);
|
|
16
|
+
const storedFormat = localStorage.getItem('sid-longitude-format');
|
|
17
|
+
if (storedFormat === 'decimal' || storedFormat === 'dms') {
|
|
18
|
+
longitudeFormat = storedFormat;
|
|
19
|
+
}
|
|
20
|
+
const storedHem = localStorage.getItem('sid-hemisphere');
|
|
21
|
+
if (storedHem === 'N' || storedHem === 'S') {
|
|
22
|
+
hemisphere = storedHem;
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function saveSettings(): void {
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem('sid-longitude', longitude.toString());
|
|
30
|
+
localStorage.setItem('sid-longitude-format', longitudeFormat);
|
|
31
|
+
localStorage.setItem('sid-hemisphere', hemisphere);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatDMS(deg: number): string {
|
|
37
|
+
const direction = deg >= 0 ? 'E' : 'W';
|
|
38
|
+
const absDeg = Math.abs(deg);
|
|
39
|
+
const degrees = Math.floor(absDeg);
|
|
40
|
+
const minutes = Math.round((absDeg - degrees) * 60);
|
|
41
|
+
return `${degrees}° ${minutes.toString().padStart(2, '0')}' ${direction}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function updateLongitudeLabel(): void {
|
|
45
|
+
const label = document.getElementById('sid-label-longitude-val');
|
|
46
|
+
if (!label) return;
|
|
47
|
+
if (longitudeFormat === 'decimal') {
|
|
48
|
+
label.textContent = `${longitude.toFixed(1)}°`;
|
|
49
|
+
} else {
|
|
50
|
+
label.textContent = formatDMS(longitude);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function updateHemisphere(): void {
|
|
55
|
+
const northGroup = document.getElementById('sid-constellations-north');
|
|
56
|
+
const southGroup = document.getElementById('sid-constellations-south');
|
|
57
|
+
if (northGroup && southGroup) {
|
|
58
|
+
if (hemisphere === 'N') {
|
|
59
|
+
northGroup.style.display = 'block';
|
|
60
|
+
southGroup.style.display = 'none';
|
|
61
|
+
} else {
|
|
62
|
+
northGroup.style.display = 'none';
|
|
63
|
+
southGroup.style.display = 'block';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function initAudio(): void {
|
|
69
|
+
if (!audioCtx) {
|
|
70
|
+
const AudioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
|
|
71
|
+
audioCtx = new AudioContextClass();
|
|
72
|
+
}
|
|
73
|
+
if (audioCtx.state === 'suspended') {
|
|
74
|
+
audioCtx.resume();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function playTick(freq: number): void {
|
|
79
|
+
if (!audioCtx || !audioEnabled) return;
|
|
80
|
+
const osc = audioCtx.createOscillator();
|
|
81
|
+
const gain = audioCtx.createGain();
|
|
82
|
+
osc.type = 'sine';
|
|
83
|
+
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
|
84
|
+
gain.gain.setValueAtTime(0.015, audioCtx.currentTime);
|
|
85
|
+
gain.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + 0.08);
|
|
86
|
+
osc.connect(gain);
|
|
87
|
+
gain.connect(audioCtx.destination);
|
|
88
|
+
osc.start();
|
|
89
|
+
osc.stop(audioCtx.currentTime + 0.08);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function updateHands(date: Date, lst: number): void {
|
|
93
|
+
const sec = date.getUTCSeconds() + date.getUTCMilliseconds() / 1000;
|
|
94
|
+
const min = date.getUTCMinutes() + sec / 60;
|
|
95
|
+
const hour = date.getUTCHours() + min / 60;
|
|
96
|
+
|
|
97
|
+
const elSec = document.getElementById('sid-hand-sec');
|
|
98
|
+
const elMin = document.getElementById('sid-hand-solar-min');
|
|
99
|
+
const elHour = document.getElementById('sid-hand-solar-hour');
|
|
100
|
+
const elSidHour = document.getElementById('sid-hand-sid-hour');
|
|
101
|
+
const elSphere = document.getElementById('sid-celestial-sphere');
|
|
102
|
+
|
|
103
|
+
if (elSec) elSec.style.transform = `rotate(${sec * 6}deg)`;
|
|
104
|
+
if (elMin) elMin.style.transform = `rotate(${min * 6}deg)`;
|
|
105
|
+
if (elHour) elHour.style.transform = `rotate(${(hour % 12) * 30}deg)`;
|
|
106
|
+
if (elSidHour) elSidHour.style.transform = `rotate(${lst * 15}deg)`;
|
|
107
|
+
if (elSphere) elSphere.style.transform = `rotate(${-lst * 15}deg)`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleTickSound(date: Date): void {
|
|
111
|
+
const solarSec = date.getUTCSeconds();
|
|
112
|
+
if (solarSec !== lastTickSecond) {
|
|
113
|
+
lastTickSecond = solarSec;
|
|
114
|
+
const isHourCross = date.getUTCMinutes() === 0 && solarSec === 0;
|
|
115
|
+
playTick(isHourCross ? 1400 : 900);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function setText(id: string, text: string): void {
|
|
120
|
+
const el = document.getElementById(id);
|
|
121
|
+
if (el) {
|
|
122
|
+
el.textContent = text;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function updateDisplay(): void {
|
|
127
|
+
const date = new Date(simulatedTime);
|
|
128
|
+
const lst = getLST(date, longitude);
|
|
129
|
+
const jd = getJulianDate(date);
|
|
130
|
+
|
|
131
|
+
handleTickSound(date);
|
|
132
|
+
updateHands(date, lst);
|
|
133
|
+
|
|
134
|
+
const utcHours = date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600;
|
|
135
|
+
let diff = Math.abs(utcHours - lst);
|
|
136
|
+
if (diff > 12) {
|
|
137
|
+
diff = 24 - diff;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setText('sid-val-solar', formatHours(utcHours));
|
|
141
|
+
setText('sid-val-sidereal', formatHours(lst));
|
|
142
|
+
setText('sid-val-drift', `${Math.floor(diff * 60)}m ${Math.floor((diff * 3600) % 60)}s`);
|
|
143
|
+
setText('sid-val-julian', jd.toFixed(5));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function tick(): void {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const delta = now - lastFrameTime;
|
|
149
|
+
lastFrameTime = now;
|
|
150
|
+
simulatedTime += delta * speedMultiplier;
|
|
151
|
+
|
|
152
|
+
updateDisplay();
|
|
153
|
+
requestAnimationFrame(tick);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function setupSpeedButtons(): void {
|
|
157
|
+
const btns = ['sid-btn-speed-1', 'sid-btn-speed-100', 'sid-btn-speed-10000'];
|
|
158
|
+
btns.forEach((id) => {
|
|
159
|
+
const el = document.getElementById(id);
|
|
160
|
+
if (!el) return;
|
|
161
|
+
el.addEventListener('click', () => {
|
|
162
|
+
btns.forEach((b) => document.getElementById(b)?.classList.remove('active'));
|
|
163
|
+
el.classList.add('active');
|
|
164
|
+
speedMultiplier = parseFloat(el.getAttribute('data-speed') || '1');
|
|
165
|
+
initAudio();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function setupFormatButtons(): void {
|
|
171
|
+
const decimalBtn = document.getElementById('sid-btn-format-decimal');
|
|
172
|
+
const dmsBtn = document.getElementById('sid-btn-format-dms');
|
|
173
|
+
if (decimalBtn && dmsBtn) {
|
|
174
|
+
decimalBtn.addEventListener('click', () => {
|
|
175
|
+
decimalBtn.classList.add('active');
|
|
176
|
+
dmsBtn.classList.remove('active');
|
|
177
|
+
longitudeFormat = 'decimal';
|
|
178
|
+
updateLongitudeLabel();
|
|
179
|
+
saveSettings();
|
|
180
|
+
});
|
|
181
|
+
dmsBtn.addEventListener('click', () => {
|
|
182
|
+
dmsBtn.classList.add('active');
|
|
183
|
+
decimalBtn.classList.remove('active');
|
|
184
|
+
longitudeFormat = 'dms';
|
|
185
|
+
updateLongitudeLabel();
|
|
186
|
+
saveSettings();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function setupHemisphereButtons(): void {
|
|
192
|
+
const northBtn = document.getElementById('sid-btn-hem-north');
|
|
193
|
+
const southBtn = document.getElementById('sid-btn-hem-south');
|
|
194
|
+
if (northBtn && southBtn) {
|
|
195
|
+
northBtn.addEventListener('click', () => {
|
|
196
|
+
northBtn.classList.add('active');
|
|
197
|
+
southBtn.classList.remove('active');
|
|
198
|
+
hemisphere = 'N';
|
|
199
|
+
updateHemisphere();
|
|
200
|
+
saveSettings();
|
|
201
|
+
});
|
|
202
|
+
southBtn.addEventListener('click', () => {
|
|
203
|
+
southBtn.classList.add('active');
|
|
204
|
+
northBtn.classList.remove('active');
|
|
205
|
+
hemisphere = 'S';
|
|
206
|
+
updateHemisphere();
|
|
207
|
+
saveSettings();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function setupLocationButton(slider: HTMLInputElement): void {
|
|
213
|
+
const locateBtn = document.getElementById('sid-btn-locate');
|
|
214
|
+
if (!locateBtn) return;
|
|
215
|
+
locateBtn.addEventListener('click', () => {
|
|
216
|
+
if (navigator.geolocation) {
|
|
217
|
+
navigator.geolocation.getCurrentPosition((pos) => {
|
|
218
|
+
const lon = pos.coords.longitude;
|
|
219
|
+
slider.value = lon.toFixed(1);
|
|
220
|
+
longitude = lon;
|
|
221
|
+
updateLongitudeLabel();
|
|
222
|
+
saveSettings();
|
|
223
|
+
}, () => {});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function setupAudioButton(): void {
|
|
229
|
+
const audioBtn = document.getElementById('sid-btn-audio');
|
|
230
|
+
if (!audioBtn) return;
|
|
231
|
+
audioBtn.addEventListener('click', () => {
|
|
232
|
+
initAudio();
|
|
233
|
+
audioEnabled = !audioEnabled;
|
|
234
|
+
audioBtn.textContent = audioEnabled ? 'ON' : 'OFF';
|
|
235
|
+
audioBtn.classList.toggle('active', audioEnabled);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function setupEventListeners(): void {
|
|
240
|
+
const slider = document.getElementById('sid-input-longitude') as HTMLInputElement;
|
|
241
|
+
if (slider) {
|
|
242
|
+
slider.value = longitude.toString();
|
|
243
|
+
slider.addEventListener('input', () => {
|
|
244
|
+
longitude = parseFloat(slider.value);
|
|
245
|
+
updateLongitudeLabel();
|
|
246
|
+
saveSettings();
|
|
247
|
+
});
|
|
248
|
+
setupLocationButton(slider);
|
|
249
|
+
}
|
|
250
|
+
setupAudioButton();
|
|
251
|
+
setupSpeedButtons();
|
|
252
|
+
setupFormatButtons();
|
|
253
|
+
setupHemisphereButtons();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let initiated = false;
|
|
257
|
+
function init(): void {
|
|
258
|
+
if (initiated) return;
|
|
259
|
+
initiated = true;
|
|
260
|
+
lastFrameTime = Date.now();
|
|
261
|
+
simulatedTime = Date.now();
|
|
262
|
+
setupEventListeners();
|
|
263
|
+
|
|
264
|
+
updateLongitudeLabel();
|
|
265
|
+
updateHemisphere();
|
|
266
|
+
|
|
267
|
+
document.getElementById('sid-btn-format-decimal')?.classList.toggle('active', longitudeFormat === 'decimal');
|
|
268
|
+
document.getElementById('sid-btn-format-dms')?.classList.toggle('active', longitudeFormat === 'dms');
|
|
269
|
+
document.getElementById('sid-btn-hem-north')?.classList.toggle('active', hemisphere === 'N');
|
|
270
|
+
document.getElementById('sid-btn-hem-south')?.classList.toggle('active', hemisphere === 'S');
|
|
271
|
+
|
|
272
|
+
requestAnimationFrame(tick);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
276
|
+
if (document.readyState !== 'loading') {
|
|
277
|
+
init();
|
|
278
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import SiderealPanel from './components/SiderealPanel.astro';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div class="tool-main-card" data-ui={JSON.stringify(ui)}>
|
|
12
|
+
<SiderealPanel labels={ui} />
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<script src="./client.ts"></script>
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../sidereal-time-tracker.css';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
labels: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { labels } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div class="sid-card">
|
|
12
|
+
<div class="sid-grid">
|
|
13
|
+
<div class="sid-dial-container">
|
|
14
|
+
<svg class="sid-svg-clock" viewBox="0 0 400 400" id="sid-watch-dial">
|
|
15
|
+
<circle cx="200" cy="200" r="195" fill="var(--sid-dial-outer-bg)" stroke="var(--sid-gold)" stroke-width="4"></circle>
|
|
16
|
+
<circle cx="200" cy="200" r="180" fill="none" stroke="var(--sid-gold-glow)" stroke-width="1"></circle>
|
|
17
|
+
|
|
18
|
+
<line x1="200" y1="8" x2="200" y2="28" stroke="var(--sid-gold)" stroke-width="1" stroke-dasharray="2 2" opacity="0.6"></line>
|
|
19
|
+
<polygon points="200,28 197,22 203,22" fill="var(--sid-gold)" opacity="0.8"></polygon>
|
|
20
|
+
<text x="200" y="16" fill="var(--sid-gold)" font-size="5" font-family="Outfit" font-weight="700" letter-spacing="0.5" text-anchor="middle" opacity="0.8">MERIDIAN</text>
|
|
21
|
+
|
|
22
|
+
<g class="sid-sky-sphere" id="sid-celestial-sphere">
|
|
23
|
+
<circle cx="200" cy="200" r="160" fill="var(--sid-dial-inner-bg)"></circle>
|
|
24
|
+
|
|
25
|
+
<g id="sid-constellations-north">
|
|
26
|
+
<line class="sid-constellation" x1="160" y1="120" x2="180" y2="100"></line>
|
|
27
|
+
<line class="sid-constellation" x1="180" y1="100" x2="205" y2="90"></line>
|
|
28
|
+
<line class="sid-constellation" x1="205" y1="90" x2="225" y2="105"></line>
|
|
29
|
+
<line class="sid-constellation" x1="225" y1="105" x2="215" y2="135"></line>
|
|
30
|
+
<line class="sid-constellation" x1="215" y1="135" x2="190" y2="140"></line>
|
|
31
|
+
<line class="sid-constellation" x1="190" y1="140" x2="160" y2="120"></line>
|
|
32
|
+
|
|
33
|
+
<line class="sid-constellation" x1="90" y1="210" x2="120" y2="190"></line>
|
|
34
|
+
<line class="sid-constellation" x1="120" y1="190" x2="130" y2="160"></line>
|
|
35
|
+
<line class="sid-constellation" x1="130" y1="160" x2="110" y2="140"></line>
|
|
36
|
+
|
|
37
|
+
<line class="sid-constellation" x1="270" y1="230" x2="290" y2="260"></line>
|
|
38
|
+
<line class="sid-constellation" x1="290" y1="260" x2="320" y2="250"></line>
|
|
39
|
+
<line class="sid-constellation" x1="320" y1="250" x2="310" y2="220"></line>
|
|
40
|
+
<line class="sid-constellation" x1="310" y1="220" x2="270" y2="230"></line>
|
|
41
|
+
|
|
42
|
+
<circle class="sid-star-blink" cx="160" cy="120" r="3" fill="var(--sid-star)"></circle>
|
|
43
|
+
<circle class="sid-star-blink" cx="180" cy="100" r="2.5" fill="var(--sid-gold)"></circle>
|
|
44
|
+
<circle class="sid-star-blink" cx="205" cy="90" r="4" fill="var(--sid-star)"></circle>
|
|
45
|
+
<circle class="sid-star-blink" cx="225" cy="105" r="2" fill="var(--sid-star)"></circle>
|
|
46
|
+
<circle class="sid-star-blink" cx="215" cy="135" r="3" fill="var(--sid-star)"></circle>
|
|
47
|
+
<circle class="sid-star-blink" cx="190" cy="140" r="2" fill="var(--sid-star)"></circle>
|
|
48
|
+
|
|
49
|
+
<circle class="sid-star-blink" cx="90" cy="210" r="3.5" fill="var(--sid-star)"></circle>
|
|
50
|
+
<circle class="sid-star-blink" cx="120" cy="190" r="2.5" fill="var(--sid-star)"></circle>
|
|
51
|
+
<circle class="sid-star-blink" cx="130" cy="160" r="3.5" fill="var(--sid-gold)"></circle>
|
|
52
|
+
<circle class="sid-star-blink" cx="110" cy="140" r="2" fill="var(--sid-star)"></circle>
|
|
53
|
+
|
|
54
|
+
<circle class="sid-star-blink" cx="270" cy="230" r="3" fill="var(--sid-star)"></circle>
|
|
55
|
+
<circle class="sid-star-blink" cx="290" cy="260" r="2" fill="var(--sid-star)"></circle>
|
|
56
|
+
<circle class="sid-star-blink" cx="320" cy="250" r="3.5" fill="var(--sid-star)"></circle>
|
|
57
|
+
<circle class="sid-star-blink" cx="310" cy="220" r="4" fill="var(--sid-gold)"></circle>
|
|
58
|
+
|
|
59
|
+
<text x="205" y="80" fill="var(--sid-sky-text)" font-size="7" text-anchor="middle">URSA MAJOR</text>
|
|
60
|
+
<text x="110" y="225" fill="var(--sid-sky-text)" font-size="7" text-anchor="middle">CASSIOPEIA</text>
|
|
61
|
+
<text x="305" y="210" fill="var(--sid-sky-text)" font-size="7" text-anchor="middle">CYGNUS</text>
|
|
62
|
+
</g>
|
|
63
|
+
|
|
64
|
+
<g id="sid-constellations-south" style="display: none;">
|
|
65
|
+
<line class="sid-constellation" x1="200" y1="90" x2="200" y2="150"></line>
|
|
66
|
+
<line class="sid-constellation" x1="170" y1="120" x2="230" y2="125"></line>
|
|
67
|
+
<circle class="sid-star-blink" cx="200" cy="90" r="3.5" fill="var(--sid-star)"></circle>
|
|
68
|
+
<circle class="sid-star-blink" cx="200" cy="150" r="4" fill="var(--sid-gold)"></circle>
|
|
69
|
+
<circle class="sid-star-blink" cx="170" cy="120" r="3.5" fill="var(--sid-star)"></circle>
|
|
70
|
+
<circle class="sid-star-blink" cx="230" cy="125" r="3" fill="var(--sid-star)"></circle>
|
|
71
|
+
<circle class="sid-star-blink" cx="215" cy="138" r="2" fill="var(--sid-star)"></circle>
|
|
72
|
+
|
|
73
|
+
<line class="sid-constellation" x1="270" y1="120" x2="310" y2="140"></line>
|
|
74
|
+
<circle class="sid-star-blink" cx="270" cy="120" r="4.5" fill="var(--sid-gold)"></circle>
|
|
75
|
+
<circle class="sid-star-blink" cx="310" cy="140" r="4" fill="var(--sid-star)"></circle>
|
|
76
|
+
|
|
77
|
+
<line class="sid-constellation" x1="100" y1="110" x2="120" y2="80"></line>
|
|
78
|
+
<line class="sid-constellation" x1="120" y1="80" x2="140" y2="100"></line>
|
|
79
|
+
<circle class="sid-star-blink" cx="100" cy="110" r="5" fill="var(--sid-gold)"></circle>
|
|
80
|
+
<circle class="sid-star-blink" cx="120" cy="80" r="3" fill="var(--sid-star)"></circle>
|
|
81
|
+
<circle class="sid-star-blink" cx="140" cy="100" r="2.5" fill="var(--sid-star)"></circle>
|
|
82
|
+
|
|
83
|
+
<text x="200" y="75" fill="var(--sid-sky-text)" font-size="7" text-anchor="middle">CRUX</text>
|
|
84
|
+
<text x="290" y="110" fill="var(--sid-sky-text)" font-size="7" text-anchor="middle">CENTAURUS</text>
|
|
85
|
+
<text x="100" y="125" fill="var(--sid-sky-text)" font-size="7" text-anchor="middle">CARINA</text>
|
|
86
|
+
</g>
|
|
87
|
+
</g>
|
|
88
|
+
|
|
89
|
+
<g fill="var(--sid-gold)" font-size="10" font-family="Outfit" font-weight="600" text-anchor="middle">
|
|
90
|
+
<text x="200" y="42">XXIV</text>
|
|
91
|
+
<text x="280" y="62">II</text>
|
|
92
|
+
<text x="338" y="120">IV</text>
|
|
93
|
+
<text x="358" y="204">VI</text>
|
|
94
|
+
<text x="338" y="288">VIII</text>
|
|
95
|
+
<text x="280" y="346">X</text>
|
|
96
|
+
<text x="200" y="366">XII</text>
|
|
97
|
+
<text x="120" y="346">XIV</text>
|
|
98
|
+
<text x="62" y="288">XVI</text>
|
|
99
|
+
<text x="42" y="204">XVIII</text>
|
|
100
|
+
<text x="62" y="120">XX</text>
|
|
101
|
+
<text x="120" y="62">XXII</text>
|
|
102
|
+
</g>
|
|
103
|
+
|
|
104
|
+
<g fill="var(--sid-indigo)" font-size="11" font-family="Outfit" text-anchor="middle" opacity="0.6">
|
|
105
|
+
<text x="200" y="70">12</text>
|
|
106
|
+
<text x="330" y="204">3</text>
|
|
107
|
+
<text x="200" y="334">6</text>
|
|
108
|
+
<text x="70" y="204">9</text>
|
|
109
|
+
</g>
|
|
110
|
+
|
|
111
|
+
<line class="sid-hand-solar" id="sid-hand-solar-hour" x1="200" y1="200" x2="200" y2="110" stroke-width="5" stroke-linecap="round"></line>
|
|
112
|
+
<line class="sid-hand-solar" id="sid-hand-solar-min" x1="200" y1="200" x2="200" y2="80" stroke-width="3" stroke-linecap="round"></line>
|
|
113
|
+
|
|
114
|
+
<g class="sid-hand-sidereal" id="sid-hand-sid-hour">
|
|
115
|
+
<line x1="200" y1="200" x2="200" y2="60" stroke="var(--sid-gold)" stroke-width="2.5" stroke-linecap="round"></line>
|
|
116
|
+
<polygon points="200,48 195,60 205,60" fill="var(--sid-gold)"></polygon>
|
|
117
|
+
</g>
|
|
118
|
+
|
|
119
|
+
<line x1="200" y1="200" x2="200" y2="52" stroke="var(--sid-second-hand)" stroke-width="1.5" id="sid-hand-sec" style="transform-origin: 200px 200px; transition: transform 0.1s linear;"></line>
|
|
120
|
+
<circle cx="200" cy="200" r="6" fill="var(--sid-gold)"></circle>
|
|
121
|
+
<circle cx="200" cy="200" r="2" fill="var(--sid-black)"></circle>
|
|
122
|
+
</svg>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div class="sid-controls-column">
|
|
126
|
+
<div class="sid-card-panel">
|
|
127
|
+
<div class="sid-readout-row">
|
|
128
|
+
<span class="sid-readout-label">{labels.solarTimeLabel}</span>
|
|
129
|
+
<span class="sid-readout-value" id="sid-val-solar">00:00:00</span>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="sid-readout-row">
|
|
132
|
+
<span class="sid-readout-label">{labels.siderealTimeLabel}</span>
|
|
133
|
+
<span class="sid-readout-value sid-gold-text" id="sid-val-sidereal">00:00:00</span>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="sid-readout-row">
|
|
136
|
+
<span class="sid-readout-label">{labels.differenceLabel}</span>
|
|
137
|
+
<span class="sid-readout-value" id="sid-val-drift">0m 00s</span>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="sid-readout-row">
|
|
140
|
+
<span class="sid-readout-label">Julian Date</span>
|
|
141
|
+
<span class="sid-readout-value" id="sid-val-julian" style="font-size: 1rem;">0.0</span>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="sid-card-panel">
|
|
146
|
+
<div class="sid-control-group">
|
|
147
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
|
148
|
+
<label class="sid-label" for="sid-input-longitude" style="margin-bottom: 0;">
|
|
149
|
+
{labels.longitudeLabel}: <span id="sid-label-longitude-val">0.0°</span>
|
|
150
|
+
</label>
|
|
151
|
+
<button type="button" class="sid-btn-location" id="sid-btn-locate" title={labels.useLocationBtn}>
|
|
152
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h-2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/></svg>
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
<input type="range" class="sid-slider" id="sid-input-longitude" min="-180" max="180" step="0.1" value="0.0" />
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="sid-control-group">
|
|
159
|
+
<label class="sid-label">{labels.longitudeFormatLabel}</label>
|
|
160
|
+
<div class="sid-speed-btn-row">
|
|
161
|
+
<button type="button" class="sid-btn-toggle active" id="sid-btn-format-decimal" data-format="decimal">{labels.longitudeFormatDecimal}</button>
|
|
162
|
+
<button type="button" class="sid-btn-toggle" id="sid-btn-format-dms" data-format="dms">{labels.longitudeFormatDMS}</button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div class="sid-control-group">
|
|
167
|
+
<label class="sid-label">{labels.hemisphereLabel}</label>
|
|
168
|
+
<div class="sid-speed-btn-row">
|
|
169
|
+
<button type="button" class="sid-btn-toggle active" id="sid-btn-hem-north" data-hem="N">{labels.hemisphereNorth}</button>
|
|
170
|
+
<button type="button" class="sid-btn-toggle" id="sid-btn-hem-south" data-hem="S">{labels.hemisphereSouth}</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div class="sid-control-group">
|
|
175
|
+
<label class="sid-label">{labels.speedLabel}</label>
|
|
176
|
+
<div class="sid-speed-btn-row">
|
|
177
|
+
<button type="button" class="sid-btn-toggle active" id="sid-btn-speed-1" data-speed="1">{labels.speedNormal}</button>
|
|
178
|
+
<button type="button" class="sid-btn-toggle" id="sid-btn-speed-100" data-speed="200">{labels.speedFast}</button>
|
|
179
|
+
<button type="button" class="sid-btn-toggle" id="sid-btn-speed-10000" data-speed="5000">{labels.speedVeryFast}</button>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div class="sid-control-group" style="margin-top: 1.5rem; display: flex; justify-content: space-between; align-items: center;">
|
|
184
|
+
<span class="sid-label" style="margin-bottom: 0;">{labels.audioToggleLabel}</span>
|
|
185
|
+
<button type="button" class="sid-btn-toggle" id="sid-btn-audio">OFF</button>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="sid-tip-box">
|
|
190
|
+
<div class="sid-tip-title">{labels.tipTitle}</div>
|
|
191
|
+
<div>{labels.step1}</div>
|
|
192
|
+
<div style="margin-top: 0.25rem;">{labels.step2}</div>
|
|
193
|
+
<div style="margin-top: 0.25rem;">{labels.step3}</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ChronoToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export type SiderealTimeTrackerUI = {
|
|
4
|
+
title: string;
|
|
5
|
+
longitudeLabel: string;
|
|
6
|
+
solarTimeLabel: string;
|
|
7
|
+
siderealTimeLabel: string;
|
|
8
|
+
differenceLabel: string;
|
|
9
|
+
utcLabel: string;
|
|
10
|
+
speedLabel: string;
|
|
11
|
+
speedNormal: string;
|
|
12
|
+
speedFast: string;
|
|
13
|
+
speedVeryFast: string;
|
|
14
|
+
audioToggleLabel: string;
|
|
15
|
+
step1: string;
|
|
16
|
+
step2: string;
|
|
17
|
+
step3: string;
|
|
18
|
+
tipTitle: string;
|
|
19
|
+
tipContent: string;
|
|
20
|
+
useLocationBtn: string;
|
|
21
|
+
hemisphereLabel: string;
|
|
22
|
+
hemisphereNorth: string;
|
|
23
|
+
hemisphereSouth: string;
|
|
24
|
+
longitudeFormatLabel: string;
|
|
25
|
+
longitudeFormatDecimal: string;
|
|
26
|
+
longitudeFormatDMS: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type SiderealTimeTrackerLocaleContent = ToolLocaleContent<SiderealTimeTrackerUI>;
|
|
30
|
+
|
|
31
|
+
export const siderealTimeTracker: ChronoToolEntry<SiderealTimeTrackerUI> = {
|
|
32
|
+
id: 'sidereal-time-tracker',
|
|
33
|
+
icons: { bg: 'mdi:orbit', fg: 'mdi:creation-outline' },
|
|
34
|
+
i18n: {
|
|
35
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
36
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
37
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
38
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
39
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
40
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
41
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
42
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
43
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
44
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
45
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
46
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
47
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
48
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
49
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
50
|
+
},
|
|
51
|
+
};
|