Asem75 commited on
Commit
d02de6c
·
verified ·
1 Parent(s): 9fb2973

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +271 -242
index.html CHANGED
@@ -3,290 +3,319 @@
3
  <head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
6
- <title>الساعة الإسلامية — اختبار طبقة الزمن</title>
7
  <style>
8
- :root{font-family:system-ui,-apple-system,"Noto Sans Arabic",Arial,sans-serif;color-scheme:dark}
9
  *{box-sizing:border-box}
10
- html,body{margin:0;min-height:100%;background:#07111d;color:#fff}
11
- body{padding:16px}
12
- header{max-width:1100px;margin:auto auto 14px}
13
- h1{font-size:1.2rem;margin:.2rem 0}
14
- p{line-height:1.7;margin:.3rem 0}
15
- .status{font-size:.95rem}
16
- .ruler-wrap{max-width:1100px;margin:auto;border:1px solid currentColor;border-radius:14px;overflow:hidden}
17
- .ruler-help{padding:12px;border-bottom:1px solid currentColor}
18
- .ruler-scroll{
19
  direction:ltr;
 
20
  overflow-x:auto;
21
  overflow-y:hidden;
22
- overscroll-behavior-x:contain;
23
- touch-action:pan-x pan-y;
 
24
  -webkit-overflow-scrolling:touch;
25
- scrollbar-gutter:stable;
 
26
  }
27
  .ruler{
28
- display:flex;
29
- align-items:stretch;
30
- width:max-content;
31
- min-height:190px;
32
- padding:12px 0;
 
 
 
33
  }
34
  .hour{
35
- direction:rtl;
36
- flex:0 0 230px;
37
- width:230px;
38
- min-height:175px;
39
- padding:18px 12px;
40
- border:1px solid currentColor;
41
- border-inline-start:0;
42
- background:transparent;
43
- color:inherit;
44
  font:inherit;
45
- text-align:center;
46
- cursor:pointer;
47
- touch-action:manipulation;
48
  }
49
- .hour:first-child{border-inline-start:1px solid currentColor}
50
- .hour:focus-visible{outline:4px solid currentColor;outline-offset:-5px}
51
- .hour .time{display:block;font-weight:800;font-size:1.22rem}
52
- .hour .day{display:block;margin-top:12px;font-size:1rem}
53
- .now-line{position:relative}
54
- .boundaries{max-width:1100px;margin:14px auto 0}
55
- .boundaries h2{font-size:1rem}
56
- .boundaries ul{padding-inline-start:24px;line-height:1.8}
57
- dialog{max-width:min(92vw,520px);border:2px solid currentColor;border-radius:14px;padding:0;background:#07111d;color:#fff}
58
- dialog::backdrop{background:rgba(0,0,0,.7)}
59
- .dialog-body{padding:18px}
60
- .close{min-width:48px;min-height:48px;font:inherit;border:1px solid currentColor;border-radius:10px;background:transparent;color:inherit}
61
- .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  </style>
63
  </head>
64
  <body>
65
- <header>
66
- <h1>الساعة الإسلامية — طبقة الزمن</h1>
67
- <p id="loadStatus" class="status" role="status" aria-live="polite">جاري تحميل سجل الزمن الفقهي…</p>
68
- </header>
69
-
70
  <main>
71
- <section class="ruler-wrap" aria-labelledby="rulerTitle">
72
- <div class="ruler-help">
73
- <strong id="rulerTitle">المسطرة الزمنية</strong>
74
- <p id="rulerInstructions">48 ساعة متصلة. يمكن تمرير المسطرة باللمس، ومع قارئ الشاشة الانتقال بين الساعات بالترتيب الزمني والضغط لفتح التفاصيل.</p>
75
- </div>
76
- <div id="rulerScroll" class="ruler-scroll">
77
- <div id="ruler" class="ruler" role="group" aria-labelledby="rulerTitle" aria-describedby="rulerInstructions"></div>
78
- </div>
79
- </section>
80
-
81
- <section class="boundaries" aria-labelledby="boundariesTitle">
82
- <h2 id="boundariesTitle">حدود سجل الزمن المحمّلة</h2>
83
- <ul id="boundaryList"></ul>
84
- </section>
85
  </main>
86
-
87
- <dialog id="hourDialog" aria-labelledby="dialogTitle" aria-describedby="dialogText">
88
- <div class="dialog-body">
89
- <h2 id="dialogTitle">تفاصيل الساعة</h2>
90
- <p id="dialogText"></p>
91
- <button id="closeDialog" class="close" type="button" aria-label="إغلاق نافذة تفاصيل الساعة">إغلاق</button>
92
- </div>
93
- </dialog>
94
-
95
  <script>
96
- "use strict";
97
-
98
- const START_HOUR = 4;
99
- const TOTAL_HOURS = 48;
100
- const PX_PER_HOUR = 230;
101
- const REGISTRY_URL = "fiqh_time_registry.json";
102
- const WORSHIP_REGISTRY_URL = "worship_fiqh_registry.json";
103
- const EVENTS_REGISTRY_URL = "islamic_events_registry.json";
104
-
105
- const ruler = document.getElementById("ruler");
106
- const rulerScroll = document.getElementById("rulerScroll");
107
- const loadStatus = document.getElementById("loadStatus");
108
- const boundaryList = document.getElementById("boundaryList");
109
- const dialog = document.getElementById("hourDialog");
110
- const dialogText = document.getElementById("dialogText");
111
- const closeDialog = document.getElementById("closeDialog");
112
- let lastFocused = null;
113
- let timeRegistry = null;
114
- let worshipRegistry = null;
115
- let eventsRegistry = null;
116
 
117
- function ar12(hour){
118
- const h = hour % 24;
119
- const period = h < 12 ? "صباحًا" : "مساءً";
120
- let n = h % 12;
121
- if(n === 0) n = 12;
 
 
 
122
  return `${n}:00 ${period}`;
123
  }
124
 
125
- function dayLabel(offsetHours){
126
- const d = new Date();
127
- d.setHours(START_HOUR + offsetHours,0,0,0);
128
- const today = new Date(); today.setHours(0,0,0,0);
129
- const dd = new Date(d); dd.setHours(0,0,0,0);
130
- const diff = Math.round((dd-today)/86400000);
131
- if(diff===0) return "اليوم";
132
- if(diff===1) return "غدًا";
133
- if(diff===2) return "بعد غد";
134
- return d.toLocaleDateString("ar",{weekday:"long",day:"numeric",month:"long"});
135
- }
136
-
137
  function buildRuler(){
138
- const frag = document.createDocumentFragment();
139
- for(let i=0;i<TOTAL_HOURS;i++){
140
- const absoluteHour = START_HOUR + i;
141
- const hour24 = absoluteHour % 24;
142
- const b = document.createElement("button");
143
- b.type="button";
144
- b.className="hour";
145
- b.dataset.index=String(i);
146
- const label=`${dayLabel(i)}، الساعة ${ar12(hour24)}`;
147
- b.setAttribute("aria-label", label);
148
- b.innerHTML=`<span class="time" aria-hidden="true">${ar12(hour24)}</span><span class="day" aria-hidden="true">${dayLabel(i)}</span>`;
149
- b.addEventListener("click",()=>openHour(b,label));
150
- frag.appendChild(b);
151
- }
152
- ruler.appendChild(frag);
153
- }
154
 
155
- function boundaryLabel(id){
156
- if(!id) return "";
157
- const bounds=Array.isArray(timeRegistry?.boundaries)?timeRegistry.boundaries:[];
158
- const x=bounds.find(v=>v.id===id);
159
- return x ? (x.label || x.label_ar || x.id) : id;
160
- }
 
 
161
 
162
- function ruleText(rule){
163
- const parts=[];
164
- const common=rule.common || {};
165
- if(common.start) parts.push(`البداية: ${boundaryLabel(common.start)}`);
166
- if(common.end) parts.push(`النهاية: ${boundaryLabel(common.end)}`);
167
- if(rule.note) parts.push(rule.note);
168
- const madhhab=rule.madhhab || {};
169
- for(const [key,value] of Object.entries(madhhab)){
170
- const name=worshipRegistry?.madhhabs?.[key] || key;
171
- if(value && typeof value === "object"){
172
- const details=[];
173
- if(value.start) details.push(`البداية ${boundaryLabel(value.start)}`);
174
- if(value.end) details.push(`النهاية ${boundaryLabel(value.end)}`);
175
- if(value.variant) details.push(value.variant);
176
- if(value.late) details.push(value.late);
177
- if(details.length) parts.push(`${name}: ${details.join("؛ ")}`);
178
  }
 
 
 
 
 
 
 
179
  }
180
- return parts.join(". ");
181
  }
 
182
 
183
- function worshipSummary(){
184
- const rules=Array.isArray(worshipRegistry?.rules)?worshipRegistry.rules:[];
185
- if(!rules.length) return "لم تُحمّل أحكام العبادات بعد.";
186
- return rules.map(r=>`${r.id}: ${ruleText(r)}`).join("\n\n");
187
- }
 
 
188
 
189
 
190
- function eventSummaryForIndex(index){
191
- const events=Array.isArray(eventsRegistry?.events)?eventsRegistry.events:[];
192
- if(!events.length) return "لم يُحمّل سجل الأحداث الإسلامية بعد.";
193
- const d=new Date();
194
- d.setHours(START_HOUR + Number(index||0),0,0,0);
195
- const weekdays=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];
196
- const wd=weekdays[d.getDay()];
197
- const matched=events.filter(ev=>{
198
- if(ev?.enabled===false) return false;
199
- const c=ev?.calendar_rule || {};
200
- if(c.type==="daily") return true;
201
- if(c.type==="weekly" && c.weekday===wd) return true;
202
- return false;
203
- });
204
- if(!matched.length) return "لا يوجد حدث يومي/أسبوعي مباشر مطابق لهذه الساعة في السجل المحمّل.";
205
- return matched.map(ev=>{
206
- const title=ev?.display?.title || ev.id || "حدث";
207
- const text=ev?.display?.text || "";
208
- return text ? `${title}: ${text}` : title;
209
- }).join("\n");
210
- }
211
 
212
- function openHour(button,label){
213
- lastFocused=button;
214
- dialogText.style.whiteSpace="pre-line";
215
- dialogText.textContent=label + "\n\nأحكام العبادات المحمّلة:\n" + worshipSummary() + "\n\nالأحداث الإسلامية المطابقة:\n" + eventSummaryForIndex(button.dataset.index);
216
- if(typeof dialog.showModal==="function") dialog.showModal();
217
- else dialog.setAttribute("open","");
218
- closeDialog.focus();
219
- }
220
 
221
- function closeHour(){
222
- if(dialog.open && typeof dialog.close==="function") dialog.close();
223
- else dialog.removeAttribute("open");
224
- if(lastFocused) lastFocused.focus({preventScroll:true});
225
- }
 
 
 
 
 
 
 
 
 
226
 
227
- closeDialog.addEventListener("click",closeHour);
228
- dialog.addEventListener("cancel",(e)=>{e.preventDefault();closeHour();});
229
- dialog.addEventListener("click",(e)=>{
230
- const r=dialog.getBoundingClientRect();
231
- const inside=e.clientX>=r.left&&e.clientX<=r.right&&e.clientY>=r.top&&e.clientY<=r.bottom;
232
- if(!inside) closeHour();
233
- });
234
 
235
- function renderBoundaries(reg){
236
- boundaryList.textContent="";
237
- const bounds=Array.isArray(reg.boundaries)?reg.boundaries:[];
238
- for(const x of bounds){
239
- const li=document.createElement("li");
240
- li.textContent=x.label || x.label_ar || x.id;
241
- boundaryList.appendChild(li);
242
- }
243
- loadStatus.textContent=`تم تحميل fiqh_time_registry.json بنجاح. عدد الحدود: ${bounds.length}.`;
244
- }
245
 
246
- async function loadRegistry(){
247
- const errors=[];
248
- try{
249
- const res=await fetch(REGISTRY_URL,{cache:"no-store"});
250
- if(!res.ok) throw new Error(`HTTP ${res.status}`);
251
- timeRegistry=await res.json();
252
- renderBoundaries(timeRegistry);
253
- }catch(err){ errors.push("fiqh_time_registry.json"); }
254
 
255
- try{
256
- const res=await fetch(WORSHIP_REGISTRY_URL,{cache:"no-store"});
257
- if(!res.ok) throw new Error(`HTTP ${res.status}`);
258
- worshipRegistry=await res.json();
259
- }catch(err){ errors.push("worship_fiqh_registry.json"); }
 
 
 
 
 
 
 
 
 
260
 
261
- try{
262
- const res=await fetch(EVENTS_REGISTRY_URL,{cache:"no-store"});
263
- if(!res.ok) throw new Error(`HTTP ${res.status}`);
264
- eventsRegistry=await res.json();
265
- }catch(err){ errors.push("islamic_events_registry.json"); }
266
 
267
- if(errors.length){
268
- loadStatus.textContent=`تعذر تحميل: ${errors.join("، ")}. المسطرة نفسها ما زالت تعمل.`;
269
- }else{
270
- const bounds=Array.isArray(timeRegistry?.boundaries)?timeRegistry.boundaries:[];
271
- const rules=Array.isArray(worshipRegistry?.rules)?worshipRegistry.rules:[];
272
- const events=Array.isArray(eventsRegistry?.events)?eventsRegistry.events:[];
273
- loadStatus.textContent=`تم تحميل سجل الزمن وسجل فقه العبادات وسجل الأحداث الإسلامية بنجاح. الحدود: ${bounds.length}، قواعد العبادات: ${rules.length}، الأحداث: ${events.length}.`;
274
- }
275
- }
 
 
 
 
 
 
 
276
 
277
- function scrollNearNow(){
278
- const now=new Date();
279
- let idx=now.getHours()-START_HOUR;
280
- if(idx<0) idx+=24;
281
- const left=Math.max(0,idx*PX_PER_HOUR-rulerScroll.clientWidth*.35);
282
- rulerScroll.scrollLeft=left;
283
- }
 
 
284
 
285
- document.addEventListener("DOMContentLoaded",()=>{
286
- buildRuler();
287
- requestAnimationFrame(scrollNearNow);
288
- loadRegistry();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  });
290
  </script>
 
291
  </body>
292
  </html>
 
3
  <head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
6
+ <title>المسطرة الزمنية</title>
7
  <style>
 
8
  *{box-sizing:border-box}
9
+ html,body{margin:0;min-height:100%;font-family:system-ui,-apple-system,"Noto Sans Arabic",sans-serif}
10
+ body{
11
+ background:linear-gradient(180deg,#0c2933,#155269);
12
+ color:#fff;padding:22px 14px;
13
+ }
14
+ main{max-width:900px;margin:auto}
15
+ h1{text-align:center;font-size:1.5rem;margin:12px 0 28px}
16
+ .ruler-shell{
 
17
  direction:ltr;
18
+ width:100%;
19
  overflow-x:auto;
20
  overflow-y:hidden;
21
+ border:1px solid rgba(255,255,255,.28);
22
+ border-radius:18px;
23
+ background:rgba(255,255,255,.08);
24
  -webkit-overflow-scrolling:touch;
25
+ scrollbar-width:thin;
26
+ touch-action:pan-x;
27
  }
28
  .ruler{
29
+ position:relative;
30
+ height:190px;
31
+ width:3600px;
32
+ min-width:3600px;
33
+ }
34
+ .axis{
35
+ position:absolute;left:0;right:0;top:86px;height:4px;
36
+ background:rgba(255,255,255,.9);
37
  }
38
  .hour{
39
+ position:absolute;top:55px;width:150px;height:105px;
40
+ transform:translateX(-75px);
41
+ border:0;background:transparent;color:#fff;padding:0;
 
 
 
 
 
 
42
  font:inherit;
 
 
 
43
  }
44
+ .tick{
45
+ position:absolute;left:50%;top:28px;width:2px;height:36px;
46
+ background:rgba(255,255,255,.85)
47
+ }
48
+ .label{
49
+ position:absolute;top:72px;left:50%;transform:translateX(-50%);
50
+ white-space:nowrap;font-size:16px;
51
+ }
52
+ .day-boundary{
53
+ position:absolute;top:26px;height:118px;width:2px;
54
+ background:rgba(255,255,255,.32)
55
+ }
56
+ .day-label{
57
+ position:absolute;top:14px;transform:translateX(-50%);
58
+ font-size:12px;opacity:.75;white-space:nowrap
59
+ }
60
+ .hour:focus-visible{outline:3px solid #fff;outline-offset:-5px;border-radius:10px}
61
+ .hint{text-align:center;opacity:.8;margin-top:15px;font-size:.9rem}
62
+
63
+ /* ===== الطبقة 2: اختبار الركن الديناميكي المحلي فقط ===== */
64
+ .dynamic-corner{
65
+ position:fixed;right:12px;bottom:12px;z-index:1000;
66
+ width:min(360px,calc(100vw - 24px));
67
+ background:#163d4b;border:1px solid rgba(255,255,255,.3);
68
+ border-radius:16px;padding:12px;box-shadow:0 10px 30px rgba(0,0,0,.35);
69
+ }
70
+ .dynamic-corner h2{font-size:17px;margin:0 0 6px}
71
+ .dynamic-corner p{margin:0 0 10px;line-height:1.6}
72
+ .dynamic-actions{display:flex;gap:8px;flex-wrap:wrap}
73
+ .dynamic-actions button{
74
+ min-height:44px;border:1px solid rgba(255,255,255,.35);
75
+ border-radius:10px;padding:8px 12px;background:#234f5e;color:#fff;font:inherit
76
+ }
77
+ #dynamicTestDialog[hidden]{display:none!important}
78
+ #dynamicTestDialog{
79
+ position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.72);
80
+ display:flex;align-items:center;justify-content:center;padding:16px
81
+ }
82
+ .dynamic-dialog-card{
83
+ width:min(560px,94vw);max-height:85vh;overflow:auto;
84
+ background:#183b48;color:#fff;border-radius:18px;padding:18px;
85
+ border:1px solid rgba(255,255,255,.28)
86
+ }
87
+ .dynamic-dialog-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
88
+ .dynamic-dialog-head h2{margin:0}
89
+ .dynamic-dialog-close{min-width:48px;min-height:48px;font-size:22px}
90
+ .dynamic-test-image{
91
+ min-height:120px;margin:14px 0;border-radius:14px;
92
+ display:flex;align-items:center;justify-content:center;
93
+ background:rgba(255,255,255,.08);font-size:42px
94
+ }
95
+
96
+
97
+ /* ===== مدير النوافذ المتكيفة ===== */
98
+ .popup-test-panel{
99
+ position:fixed;right:12px;bottom:12px;z-index:900;width:min(390px,calc(100vw - 24px));
100
+ background:#163d4b;border:1px solid rgba(255,255,255,.3);border-radius:16px;padding:12px
101
+ }
102
+ .popup-test-panel h2{font-size:17px;margin:0 0 8px}
103
+ .popup-test-panel button,#adaptivePopupActions button{
104
+ min-height:44px;margin:4px;border-radius:10px;border:1px solid rgba(255,255,255,.3);
105
+ background:#255363;color:#fff;padding:8px 12px;font:inherit
106
+ }
107
+ #adaptivePopup[hidden]{display:none!important}
108
+ #adaptivePopup{
109
+ position:fixed;inset:0;z-index:3000;background:rgba(0,0,0,.72);
110
+ display:flex;align-items:center;justify-content:center;padding:14px
111
+ }
112
+ #adaptivePopupCard{
113
+ width:min(92vw,620px);
114
+ /* الحجم يتبع المحتوى حتى هذا الحد فقط */
115
+ max-height:88vh;overflow:auto;
116
+ background:#183b48;color:#fff;border-radius:20px;padding:18px;
117
+ border:1px solid rgba(255,255,255,.28);box-shadow:0 16px 55px rgba(0,0,0,.45)
118
+ }
119
+ #adaptivePopupCard[data-size="short"]{width:min(90vw,440px)}
120
+ #adaptivePopupCard[data-size="medium"]{width:min(92vw,560px)}
121
+ #adaptivePopupCard[data-size="long"]{width:min(94vw,680px)}
122
+ .adaptive-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
123
+ .adaptive-head h2{margin:3px 0 0;font-size:1.45rem}
124
+ .adaptive-kind{font-size:.82rem;opacity:.75}
125
+ #adaptivePopupClose{min-width:48px;min-height:48px;font-size:23px}
126
+ #adaptivePopupMedia:empty{display:none}
127
+ #adaptivePopupMedia{margin:14px 0;text-align:center;font-size:42px}
128
+ #adaptivePopupBody{
129
+ white-space:pre-wrap;line-height:1.95;font-size:17px;margin:14px 0;
130
+ overflow-wrap:anywhere
131
+ }
132
+ #adaptivePopupActions{display:flex;flex-wrap:wrap;gap:6px}
133
+
134
  </style>
135
  </head>
136
  <body>
 
 
 
 
 
137
  <main>
138
+ <h1>المسطرة الزمنية</h1>
139
+ <div id="shell" class="ruler-shell" aria-label="مسطرة زمنية أفقية متصلة">
140
+ <div id="ruler" class="ruler"></div>
141
+ </div>
142
+ <p class="hint">اسحب المسطرة يمينًا أو يسارًا</p>
 
 
 
 
 
 
 
 
 
143
  </main>
 
 
 
 
 
 
 
 
 
144
  <script>
145
+ const START_HOUR = 4; // بداية العرض الخام: 4 صباحًا
146
+ const TOTAL_HOURS = 48; // يومان متصلان لإظهار الاستمرار لليوم التالي
147
+ const PX_PER_HOUR = 150;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ function twelveHourLabel(h){
150
+ const hour24=((h%24)+24)%24;
151
+ let n=hour24%12; if(n===0)n=12;
152
+ let period;
153
+ if(hour24===0) period='منتصف الليل';
154
+ else if(hour24===12) period='ظهرًا';
155
+ else if(hour24<12) period='صباحًا';
156
+ else period='مساءً';
157
  return `${n}:00 ${period}`;
158
  }
159
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  function buildRuler(){
161
+ const ruler=document.getElementById('ruler');
162
+ ruler.style.width=(TOTAL_HOURS*PX_PER_HOUR)+'px';
163
+ ruler.style.minWidth=(TOTAL_HOURS*PX_PER_HOUR)+'px';
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
+ const axis=document.createElement('div');
166
+ axis.className='axis'; axis.setAttribute('aria-hidden','true');
167
+ ruler.appendChild(axis);
168
+
169
+ for(let i=0;i<=TOTAL_HOURS;i++){
170
+ const absolute=START_HOUR+i;
171
+ const h=absolute%24;
172
+ const x=i*PX_PER_HOUR;
173
 
174
+ if(i>0 && h===0){
175
+ const b=document.createElement('div');
176
+ b.className='day-boundary';b.style.left=x+'px';b.setAttribute('aria-hidden','true');
177
+ ruler.appendChild(b);
178
+ const dl=document.createElement('div');
179
+ dl.className='day-label';dl.style.left=x+'px';dl.textContent='اليوم التالي';
180
+ dl.setAttribute('aria-hidden','true');ruler.appendChild(dl);
 
 
 
 
 
 
 
 
 
181
  }
182
+
183
+ const el=document.createElement('button');
184
+ el.type='button';el.className='hour';el.style.left=x+'px';
185
+ const label=twelveHourLabel(h);
186
+ el.setAttribute('aria-label',label);
187
+ el.innerHTML=`<span class="tick" aria-hidden="true"></span><span class="label" aria-hidden="true">${label}</span>`;
188
+ ruler.appendChild(el);
189
  }
 
190
  }
191
+ buildRuler();
192
 
193
+ // افتح العرض قرب الوقت الحالي، مع بقاء المسطرة قابلة للسحب في الاتجاهين.
194
+ requestAnimationFrame(()=>{
195
+ const now=new Date();
196
+ let offset=now.getHours()-START_HOUR;
197
+ if(offset<0)offset+=24;
198
+ document.getElementById('shell').scrollLeft=Math.max(0,(offset-2)*PX_PER_HOUR);
199
+ });
200
 
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ </script>
 
 
 
 
 
 
 
204
 
205
+ <div id="dynamicTestDialog" hidden role="dialog" aria-modal="true" aria-labelledby="dynamicTestTitle">
206
+ <article class="dynamic-dialog-card">
207
+ <div class="dynamic-dialog-head">
208
+ <h2 id="dynamicTestTitle">اختبار الركن الديناميكي</h2>
209
+ <button id="closeDynamicTest" class="dynamic-dialog-close" type="button" aria-label="إغلاق هذا فقط">✕</button>
210
+ </div>
211
+ <div class="dynamic-test-image" role="img" aria-label="صورة تجريبية للهلال">🌙</div>
212
+ <p>إذا ظهرت هذه النافذة بعد الضغط على «فتح التنبيه»، فهذا يثبت أن شاشة الركن الديناميكي المحلية تعمل داخل ملف HTML نفسه.</p>
213
+ <div class="dynamic-actions">
214
+ <button id="closeDynamicTestBottom" type="button">إغلاق هذا فقط</button>
215
+ <button id="dismissAllFromDialog" type="button">تجاهل الكل</button>
216
+ </div>
217
+ </article>
218
+ </div>
219
 
 
 
 
 
 
 
 
220
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ <section class="popup-test-panel" aria-label="اختبار الشاشات الديناميكية">
223
+ <h2>اختبار الشاشات</h2>
224
+ <button type="button" onclick="PopupManager.open('worship', DEMO.worship)">عبادات — نص صغير</button>
225
+ <button type="button" onclick="PopupManager.open('quran', DEMO.quran)">القرآن — نص متوسط</button>
226
+ <button type="button" onclick="PopupManager.open('content', DEMO.content)">اشتراكات — نص كبير</button>
227
+ </section>
 
 
228
 
229
+ <div id="adaptivePopup" hidden role="dialog" aria-modal="true" aria-labelledby="adaptivePopupTitle">
230
+ <article id="adaptivePopupCard">
231
+ <header class="adaptive-head">
232
+ <div>
233
+ <div id="adaptivePopupKind" class="adaptive-kind"></div>
234
+ <h2 id="adaptivePopupTitle"></h2>
235
+ </div>
236
+ <button id="adaptivePopupClose" type="button" aria-label="إغلاق هذا فقط">✕</button>
237
+ </header>
238
+ <div id="adaptivePopupMedia"></div>
239
+ <div id="adaptivePopupBody"></div>
240
+ <div id="adaptivePopupActions"></div>
241
+ </article>
242
+ </div>
243
 
 
 
 
 
 
244
 
245
+ <script>
246
+ const DEMO={
247
+ worship:{
248
+ title:'تذكير عبادة',
249
+ text:'تذكير قصير لصلاة الضحى.',
250
+ media:'☀️'
251
+ },
252
+ quran:{
253
+ title:'سورة الكهف',
254
+ text:'هذه نافذة تجريبية للقرآن. يمكن لاحقًا أن تعرض نص السورة أو الآية، مع أزرار القراءة و��لاستماع والتنقل. حجم النافذة الآن يتغير تلقائيًا بحسب كمية النص الموجودة فيها.',
255
+ media:'📖',
256
+ actions:['قراءة','استماع']
257
+ },
258
+ content:{
259
+ title:'تفسير ومحتوى مشترك',
260
+ text:`هذا اختبار لنص طويل داخل شاشة الاشتراكات والمحتوى الديناميكي.
261
 
262
+ الفكرة هنا أن النافذة لا تملك ارتفاعًا ثابتًا. إذا كان المحتوى سطرًا أو سطرين تبقى صغيرة، وإذا كان تفسيرًا أو مقالة أو مادة معرفية أطول فإنها تكبر تدريجيًا مع النص.
263
+
264
+ وعندما يصبح النص أطول من مساحة شاشة الهاتف، تتوقف النافذة عن التمدد عند الحد الآمن وتصبح هي نفسها قابلة للتمرير، بدل أن يخرج المحتوى خارج الشاشة أو تصبح أزرار الإغلاق غير قابلة للوصول.
265
+
266
+ هذا نص تجريبي فقط لإظهار اختلاف الحجم، وليس محتوى دينيًا فعليًا.`,
267
+ media:'📰',
268
+ actions:['حفظ للقراءة لاحقًا','فتح المصدر']
269
+ }
270
+ };
271
 
272
+ const PopupManager=(()=>{
273
+ let lastFocus=null;
274
+ const labels={worship:'العبادات والمناسبات',quran:'القرآن',content:'الإعلانات والاشتراكات'};
275
+ function sizeFor(text){
276
+ const n=(text||'').length;
277
+ if(n<90)return 'short';
278
+ if(n<420)return 'medium';
279
+ return 'long';
280
+ }
281
+ function open(kind,data){
282
+ lastFocus=document.activeElement;
283
+ const overlay=document.getElementById('adaptivePopup');
284
+ const card=document.getElementById('adaptivePopupCard');
285
+ const body=document.getElementById('adaptivePopupBody');
286
+ document.getElementById('adaptivePopupKind').textContent=labels[kind]||'محتوى';
287
+ document.getElementById('adaptivePopupTitle').textContent=data.title||'';
288
+ body.textContent=data.text||'';
289
+ const media=document.getElementById('adaptivePopupMedia');
290
+ media.textContent=data.media||'';
291
+ const actions=document.getElementById('adaptivePopupActions');
292
+ actions.innerHTML='';
293
+ (data.actions||[]).forEach(label=>{
294
+ const b=document.createElement('button');b.type='button';b.textContent=label;
295
+ b.addEventListener('click',()=>alert('زر تجريبي: '+label));
296
+ actions.appendChild(b);
297
+ });
298
+ card.dataset.size=sizeFor(data.text);
299
+ overlay.hidden=false;
300
+ document.body.style.overflow='hidden';
301
+ document.getElementById('adaptivePopupClose').focus();
302
+ }
303
+ function close(){
304
+ document.getElementById('adaptivePopup').hidden=true;
305
+ document.body.style.overflow='';
306
+ if(lastFocus&&lastFocus.focus)lastFocus.focus();
307
+ }
308
+ return {open,close};
309
+ })();
310
+
311
+ document.getElementById('adaptivePopupClose').addEventListener('click',PopupManager.close);
312
+ document.getElementById('adaptivePopup').addEventListener('click',e=>{
313
+ if(e.target.id==='adaptivePopup')PopupManager.close();
314
+ });
315
+ document.addEventListener('keydown',e=>{
316
+ if(e.key==='Escape'&&!document.getElementById('adaptivePopup').hidden)PopupManager.close();
317
  });
318
  </script>
319
+
320
  </body>
321
  </html>