Asem75 commited on
Commit
561c8ae
·
verified ·
1 Parent(s): db459cf

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +242 -0
index.html CHANGED
@@ -137,11 +137,47 @@ body{padding-bottom:190px}
137
  .now-marker-label{position:absolute;top:0;left:0;width:270px;max-width:calc(100vw - 32px);white-space:pre-line;text-align:center;line-height:1.65;background:var(--cell-surface);border:1px solid currentColor;border-radius:8px;padding:4px;direction:rtl}
138
  .personal-hour{pointer-events:auto;z-index:3;border:1px solid var(--cell-border);border-radius:5px;color:inherit;background:var(--cell-surface);font:inherit;cursor:pointer}
139
  #reminderNotice{position:fixed;top:8px;left:12px;right:12px;z-index:2900;max-height:24vh;overflow:auto;margin:0;border:2px solid currentColor}#reminderDialogNotice{flex-shrink:0;padding:12px;font:inherit;background:var(--cell-surface);color:inherit;border:2px solid currentColor;max-height:20vh;overflow:auto}#reminderDialogNotice[hidden]{display:none}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  </style>
141
  </head>
142
  <body>
143
  <main>
144
  <h1 data-ui="title"></h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  <div class="timeline-navigation"><span id="timelineDates"></span> <button id="timelineToday" type="button" data-ui="timelineToday" hidden></button></div>
146
  <div id="shell" class="ruler-shell" data-aria="ruler">
147
  <div id="ruler" class="ruler"></div>
@@ -176,5 +212,211 @@ body{padding-bottom:190px}
176
  <script src="hijri_calendar_engine.js?v=20260926-worship1"></script>
177
  <script src="worship_event_engine.js?v=20260926-worship1"></script>
178
  <script src="registry_app.js?v=20260927-markers2"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  </body>
180
  </html>
 
137
  .now-marker-label{position:absolute;top:0;left:0;width:270px;max-width:calc(100vw - 32px);white-space:pre-line;text-align:center;line-height:1.65;background:var(--cell-surface);border:1px solid currentColor;border-radius:8px;padding:4px;direction:rtl}
138
  .personal-hour{pointer-events:auto;z-index:3;border:1px solid var(--cell-border);border-radius:5px;color:inherit;background:var(--cell-surface);font:inherit;cursor:pointer}
139
  #reminderNotice{position:fixed;top:8px;left:12px;right:12px;z-index:2900;max-height:24vh;overflow:auto;margin:0;border:2px solid currentColor}#reminderDialogNotice{flex-shrink:0;padding:12px;font:inherit;background:var(--cell-surface);color:inherit;border:2px solid currentColor;max-height:20vh;overflow:auto}#reminderDialogNotice[hidden]{display:none}
140
+ /* Simulation stays in document flow, below dialogs and reminder notices. */
141
+ #celestialSection{margin:16px 0 24px}
142
+ #celestialSection h2{font-size:1.1rem;margin:0 0 10px}
143
+ #celestialControls{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0}
144
+ #celestialControls button{font:inherit;min-height:44px;padding:8px 12px;border:1px solid var(--cell-border,#888);border-radius:8px;background:var(--cell-surface,#fff);color:inherit;cursor:pointer}
145
+ #celestialControls button:disabled{opacity:.6;cursor:default}
146
+ #celestialScene{position:relative;height:240px;overflow:hidden;isolation:isolate;direction:ltr}
147
+ #celestialScene[hidden]{display:none}
148
+ .celestial-body{position:absolute;border-radius:50%;pointer-events:none;opacity:0}
149
+ #celestialSun{background:#ffd45c;box-shadow:0 0 20px #ffd45c99}
150
+ #celestialGround{position:absolute;inset:82% 0 0;background:#477552;border-top:1px solid #ffffff80;pointer-events:none}
151
+ #celestialStick{position:absolute;left:50%;bottom:18%;height:38px;width:5px;background:#eee;z-index:1}
152
+ #celestialShadow{position:absolute;left:50%;bottom:18%;height:5px;background:#111;transform-origin:left center}
153
+ #celestialQibla{position:absolute;left:50%;top:50%;width:36px;height:40px;background:#191919;border:1px solid #eee;border-radius:3px;pointer-events:none}
154
+ #celestialQibla::before{content:"";position:absolute;left:0;right:0;top:8px;height:6px;background:#d7b56d}
155
+ #celestialInfo,#celestialLocation,#celestialStatus{margin:8px 0;line-height:1.7;overflow-wrap:anywhere;white-space:pre-line;font-size:14px}
156
+ #celestialStatus:empty{display:none}
157
  </style>
158
  </head>
159
  <body>
160
  <main>
161
  <h1 data-ui="title"></h1>
162
+ <section id="celestialSection" aria-labelledby="celestialTitle">
163
+ <h2 id="celestialTitle">محاكاة الشمس والقمر والقبلة</h2>
164
+ <div id="celestialScene" hidden role="img" aria-label="محاكاة السماء حسب الموقع والوقت الحالي">
165
+ <div id="celestialSun" class="celestial-body"></div>
166
+ <div id="celestialMoon" class="celestial-body"></div>
167
+ <div id="celestialGround"></div>
168
+ <div id="celestialShadow"></div>
169
+ <div id="celestialStick"></div>
170
+ <div id="celestialQibla" hidden></div>
171
+ </div>
172
+ <p id="celestialLocation"></p>
173
+ <p id="celestialInfo"></p>
174
+ <div id="celestialControls">
175
+ <button id="celestialLocate" type="button" disabled>تحديد موقع المحاكاة</button>
176
+ <button id="celestialCompass" type="button" disabled>تفعيل اتجاه القبلة</button>
177
+ <button id="celestialRetry" type="button" hidden>إعادة المحاولة</button>
178
+ </div>
179
+ <p id="celestialStatus" role="status">جارٍ تحميل المحاكاة...</p>
180
+ </section>
181
  <div class="timeline-navigation"><span id="timelineDates"></span> <button id="timelineToday" type="button" data-ui="timelineToday" hidden></button></div>
182
  <div id="shell" class="ruler-shell" data-aria="ruler">
183
  <div id="ruler" class="ruler"></div>
 
212
  <script src="hijri_calendar_engine.js?v=20260926-worship1"></script>
213
  <script src="worship_event_engine.js?v=20260926-worship1"></script>
214
  <script src="registry_app.js?v=20260927-markers2"></script>
215
+ <script>
216
+ (function () {
217
+ "use strict";
218
+ const byId = (id) => document.getElementById(id);
219
+ const scene = byId("celestialScene");
220
+ const status = byId("celestialStatus");
221
+ const locate = byId("celestialLocate");
222
+ const compass = byId("celestialCompass");
223
+ const retry = byId("celestialRetry");
224
+ const locationLabel = byId("celestialLocation");
225
+ let controller;
226
+ let timer;
227
+ let compassTimer;
228
+ let compassActive = false;
229
+ let compassListening = false;
230
+ let loading = false;
231
+ let loadAttempt = 0;
232
+
233
+ function validLocation(value) {
234
+ return value && Number.isFinite(value.lat) && Number.isFinite(value.lng)
235
+ && Math.abs(value.lat) <= 90 && Math.abs(value.lng) <= 180;
236
+ }
237
+
238
+ function initialLocation() {
239
+ const provided = window.MUSLIM_CLOCK_LOCATION || window.ISLAMIC_CLOCK_LOCATION;
240
+ if (validLocation(provided)) return { value: provided, label: "موقع المحاكاة: موقع التطبيق" };
241
+ try {
242
+ const saved = JSON.parse(localStorage.getItem("muslim_clock_location"));
243
+ if (validLocation(saved)) return { value: saved, label: "موقع المحاكاة: الموقع المحفوظ" };
244
+ } catch (_) { /* Storage can be unavailable in embedded browsers. */ }
245
+ return { value: { lat: 31.9539, lng: 35.9106 }, label: "موقع المحاكاة الافتراضي: عمّان (لم يُحدَّد موقع الجهاز)" };
246
+ }
247
+
248
+ function showLocation(value, label) {
249
+ locationLabel.textContent = `${label} · ${value.lat.toFixed(4)}, ${value.lng.toFixed(4)}`;
250
+ }
251
+
252
+ function update() {
253
+ if (!controller) return;
254
+ try {
255
+ controller.update();
256
+ } catch (error) {
257
+ status.textContent = "تعذر تحديث المحاكاة. أعد المحاولة.";
258
+ retry.hidden = false;
259
+ console.error("Celestial simulation update:", error);
260
+ }
261
+ }
262
+
263
+ async function boot() {
264
+ if (loading) return;
265
+ loading = true;
266
+ retry.hidden = true;
267
+ status.textContent = "جارٍ تحميل المحاكاة...";
268
+ try {
269
+ if (!window.CelestialSimulation) {
270
+ try {
271
+ // Module scope prevents collisions with the existing solar engine.
272
+ await import(`./celestial_simulation.js?v=20260928-integration1&attempt=${++loadAttempt}`);
273
+ if (!window.CelestialSimulation) throw new Error("Missing simulation API");
274
+ } catch (_) {
275
+ throw new Error("تعذر تحميل celestial_simulation.js. تأكد من وجوده بجانب index.html وبالاسم نفسه، دون .txt.");
276
+ }
277
+ }
278
+ let registry;
279
+ try {
280
+ const response = await fetch("./sky_simulation_registry.json", { cache: "no-store" });
281
+ if (!response.ok) throw new Error("Registry HTTP " + response.status);
282
+ registry = await response.json();
283
+ if (!Array.isArray(registry.sky_color_model?.gradient_stops)
284
+ || !registry.sky_color_model.gradient_stops.length || !registry.objects) {
285
+ throw new Error("Invalid sky registry");
286
+ }
287
+ } catch (_) {
288
+ throw new Error("تعذر قراءة sky_simulation_registry.json. تأكد من اسمه ووجوده بجانب index.html وصحة محتواه.");
289
+ }
290
+ const api = window.CelestialSimulation;
291
+ const renderer = new api.SkySimulationRenderer({
292
+ container: scene, sun: byId("celestialSun"), moon: byId("celestialMoon"),
293
+ shadow: byId("celestialShadow"), info: byId("celestialInfo"),
294
+ kaaba: byId("celestialQibla")
295
+ }, registry);
296
+ const render = renderer.render.bind(renderer);
297
+ renderer.render = (state) => {
298
+ render(state);
299
+ // Keep projected discs fully inside the scene at east/west edges.
300
+ for (const id of ["celestialSun", "celestialMoon"]) {
301
+ const element = byId(id);
302
+ const inset = parseFloat(element.style.width) / 2 + 2;
303
+ element.style.left = `clamp(${inset}px, ${element.style.left}, calc(100% - ${inset}px))`;
304
+ }
305
+ const qibla = byId("celestialQibla");
306
+ qibla.hidden = !state.compassEnabled;
307
+ if (!state.compassEnabled) qibla.title = "اتجاه الهاتف غير متاح";
308
+ scene.setAttribute("aria-label", byId("celestialInfo").textContent);
309
+ };
310
+ const selected = controller
311
+ ? { value: controller.location, label: "موقع المحاكاة الحالي" }
312
+ : initialLocation();
313
+ const oldHeading = controller?.headingDeg;
314
+ controller = new api.CelestialSimulationController({ location: selected.value, registry, renderer });
315
+ if (Number.isFinite(oldHeading)) controller.setHeading(oldHeading);
316
+ window.MuslimClockCelestialController = controller;
317
+ showLocation(selected.value, selected.label);
318
+ scene.hidden = false;
319
+ controller.update();
320
+ clearInterval(timer);
321
+ timer = setInterval(update, 60000);
322
+ locate.disabled = false;
323
+ compass.disabled = compassListening;
324
+ status.textContent = "";
325
+ } catch (error) {
326
+ status.textContent = error.message;
327
+ retry.hidden = false;
328
+ } finally {
329
+ loading = false;
330
+ }
331
+ }
332
+
333
+ locate.addEventListener("click", () => {
334
+ if (!navigator.geolocation) {
335
+ status.textContent = "تحديد الموقع غير متاح في هذا المتصفح.";
336
+ return;
337
+ }
338
+ locate.disabled = true;
339
+ status.textContent = "جارٍ تحديد موقع الجهاز للمحاكاة...";
340
+ navigator.geolocation.getCurrentPosition((position) => {
341
+ const location = { lat: position.coords.latitude, lng: position.coords.longitude };
342
+ controller.setLocation(location);
343
+ showLocation(location, "موقع المحاكاة: موقع الجهاز");
344
+ status.textContent = "";
345
+ locate.disabled = false;
346
+ update();
347
+ }, () => {
348
+ status.textContent = "تعذر تحديد الموقع أو لم يُمنح الإذن. بقي الموقع المبين أعلاه دون تغيير.";
349
+ locate.disabled = false;
350
+ }, { enableHighAccuracy: true, timeout: 15000, maximumAge: 60000 });
351
+ });
352
+
353
+ function orientation(event) {
354
+ // Relative alpha is not a north-referenced compass bearing.
355
+ const heading = Number.isFinite(event.webkitCompassHeading)
356
+ ? event.webkitCompassHeading
357
+ : event.absolute && Number.isFinite(event.alpha) ? 360 - event.alpha : null;
358
+ if (!Number.isFinite(heading) || !controller) return;
359
+ clearTimeout(compassTimer);
360
+ if (!compassActive) {
361
+ compassActive = true;
362
+ status.textContent = "";
363
+ compass.textContent = "اتجاه القبلة مفعّل";
364
+ }
365
+ controller.setHeading(heading);
366
+ // Reproject the current snapshot without repeating astronomy on every sensor event.
367
+ const state = controller.lastState;
368
+ if (state) {
369
+ state.headingDeg = controller.headingDeg;
370
+ state.compassEnabled = true;
371
+ state.qibla = window.CelestialSimulation.QiblaEngine.calculate(controller.location,
372
+ controller.headingDeg, { alignmentThresholdDeg: controller.registry.objects.kaaba?.alignment_threshold_deg });
373
+ controller.renderer.render(state);
374
+ }
375
+ }
376
+
377
+ compass.addEventListener("click", async () => {
378
+ if (!window.isSecureContext || typeof DeviceOrientationEvent === "undefined") {
379
+ status.textContent = "حساس الاتجاه غير متاح. افتح التطبيق عبر HTTPS على هاتف يدعم البوصلة.";
380
+ return;
381
+ }
382
+ compass.disabled = true;
383
+ try {
384
+ if (typeof DeviceOrientationEvent.requestPermission === "function"
385
+ && await DeviceOrientationEvent.requestPermission() !== "granted") {
386
+ throw new Error("لم يُمنح إذن حساس الاتجاه.");
387
+ }
388
+ if (!compassListening) {
389
+ window.addEventListener("deviceorientationabsolute", orientation, true);
390
+ window.addEventListener("deviceorientation", orientation, true);
391
+ compassListening = true;
392
+ }
393
+ status.textContent = "بانتظار قراءة بوصلة الهاتف...";
394
+ compassTimer = setTimeout(() => {
395
+ if (compassActive) return;
396
+ window.removeEventListener("deviceorientationabsolute", orientation, true);
397
+ window.removeEventListener("deviceorientation", orientation, true);
398
+ compassListening = false;
399
+ compass.disabled = false;
400
+ status.textContent = "لم تصل قراءة اتجاه مطلقة. يبقى اتجاه القبلة الرقمي معروضًا؛ جرّب فتح التطبيق مباشرة خارج الإطار.";
401
+ }, 8000);
402
+ } catch (error) {
403
+ compass.disabled = false;
404
+ status.textContent = error.message || "تعذر تفعيل البوصلة.";
405
+ }
406
+ });
407
+
408
+ retry.addEventListener("click", boot);
409
+ document.addEventListener("visibilitychange", () => {
410
+ if (!document.hidden) update();
411
+ });
412
+ window.addEventListener("muslim-clock-location-change", (event) => {
413
+ if (!controller || !validLocation(event.detail)) return;
414
+ controller.setLocation(event.detail);
415
+ showLocation(event.detail, "موقع المحاكاة: موقع التطبيق");
416
+ update();
417
+ });
418
+ boot();
419
+ })();
420
+ </script>
421
  </body>
422
  </html>