Spaces:
Running
Running
Upload index.html
Browse files- 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>ال
|
| 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%;
|
| 11 |
-
body{
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
.ruler-
|
| 18 |
-
.ruler-scroll{
|
| 19 |
direction:ltr;
|
|
|
|
| 20 |
overflow-x:auto;
|
| 21 |
overflow-y:hidden;
|
| 22 |
-
|
| 23 |
-
|
|
|
|
| 24 |
-webkit-overflow-scrolling:touch;
|
| 25 |
-
scrollbar-
|
|
|
|
| 26 |
}
|
| 27 |
.ruler{
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
width:
|
| 31 |
-
min-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
.hour{
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 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 |
-
.
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
.
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
<
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 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 |
-
|
| 97 |
-
|
| 98 |
-
const
|
| 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
|
| 118 |
-
const
|
| 119 |
-
|
| 120 |
-
let
|
| 121 |
-
if(
|
|
|
|
|
|
|
|
|
|
| 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
|
| 139 |
-
|
| 140 |
-
|
| 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 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 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 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 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 |
-
|
| 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 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 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 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
renderBoundaries(timeRegistry);
|
| 253 |
-
}catch(err){ errors.push("fiqh_time_registry.json"); }
|
| 254 |
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
|
|
|
|
|
|
| 284 |
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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>
|