PageSourceSearch

https://qudsn.co/contact_us

html qudsn.co collected 2026-09-24 23:10:47 UTC 135,809 bytes, 2,491 lines download raw bytes

1<!DOCTYPE html>
2<html lang="ar" dir="rtl">
3
4<head>
5    <meta charset="utf-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
7
8    
9    <title>شبكة قدس الإخبارية - تواصل معنا</title>
10    <meta name="title" content="شبكة قدس الإخبارية - تواصل معنا" />
11    <meta name="description" content=" نقدم المعالجات الإخبارية والتحليلات المعمقة المرتبطة بالشأن الفلسطيني وتأثيراته إقليميا ودوليا عبر قوالب تركز على المهنية" />
12    <meta name="author" content="شبكة قدس الإخبارية" />
13    <meta name="keywords" content="شبكة قدس,وكالة قدس,صحيفة القدس,jerusalem,أخبار القدس,القدس عاصمة,أخبار,فلسطين,غزة,القدس الشرقية,القدس الغربية,قدسنا,الضفة,فيسبوك قدس,قدس الإخبارية,خبر,خبر برس,برس,صفحة فيسبوك,أخبار فيسبوك,أنباء قدس,أورشاليم,جيروزاليم,إقتحام,إنفجار,قصف,إختطاف,إعتقال,معتقل,أسير,أسرى,الإحتلال,قدس نت,انتخابات,انتخابات فلسطين,حماس,فتح,الجهاد,سرايا,قسام,القدس" />
14    <meta name="theme-color" content="#2b74bd">
15
16    
17    <meta property="og:type" content="website" />
18    <meta property="og:site_name" content="شبكة قدس الإخبارية" />
19    <meta property="og:title" content="تواصل معنا" />
20    <meta property="og:description" content="شبكة قدس الإخبارية، عاجل وتحليلات الأخبار من القدس وفلسطين إلى العالم الآن | وكالة، صحيفة إلكترونية، موقع، فيسبوك، نت Quds News Network" />
21    <meta property="og:locale" content="ar_AR" />
22        <meta property="og:image" content="https://qudsn.co/og/02dbfcf970eda8d246bd275c321840b0407d1f5b.jpg" />
23    <meta property="og:image:secure_url" content="https://qudsn.co/og/02dbfcf970eda8d246bd275c321840b0407d1f5b.jpg" />
24        <meta property="og:image:alt" content="تواصل معنا" />
25    <meta property="og:url" content="https://qudsn.co/contact_us" />
26
27    
28    <meta name="twitter:card" content="summary_large_image" />
29    <meta name="twitter:title" content="تواصل معنا" />
30    <meta name="twitter:description" content="شبكة قدس الإخبارية، عاجل وتحليلات الأخبار من القدس وفلسطين إلى العالم الآن | وكالة، صحيفة إلكترونية، موقع، فيسبوك، نت Quds News Network" />
31    <meta name="twitter:image" content="https://qudsn.co/og/02dbfcf970eda8d246bd275c321840b0407d1f5b.jpg" />
32    <meta name="twitter:image:alt" content="تواصل معنا" />
33            <meta name="twitter:site" content="@QudsN" />
34                <meta name="twitter:creator" content="@QudsN" />
35    
36    
37    <link rel="canonical" href="https://qudsn.co/contact_us" />
38    <meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
39
40    
41    <link rel="alternate" type="application/rss+xml" title="شبكة قدس الإخبارية RSS"
42        href="https://qudsn.co/rss/category/all" />
43
44    
45    
45<script type="application/ld+json">
46    {"@context":"https://schema.org","@type":"NewsMediaOrganization","name":"شبكة قدس الإخبارية","url":"https://qudsn.co","logo":"https://fsn1.your-objectstorage.com/qudsn/uploads/2026/08/file_6a6f0b281cbc8.png"}
47    </script>
47
48    
48<script type="application/ld+json">
49    {"@context":"https://schema.org","@type":"WebSite","name":"شبكة قدس الإخبارية","url":"https://qudsn.co","inLanguage":"ar","potentialAction":{"@type":"SearchAction","target":"https://qudsn.co/search_page?search={search_term_string}","query-input":"required name=search_term_string"}}
50    </script>
50
51    
52    <link rel="shortcut icon" type="image/png"
53        href="https://fsn1.your-objectstorage.com/qudsn/uploads/2026/08/file_6a6f0b463c00e.ico" />
54
55    
56    
57    
58            <link rel="preload" href="/assets/main/quds_ar/fonts/ibm-plex-sans-arabic/plex-arabic-400-core.woff2" as="font" type="font/woff2" crossorigin>
59            <link rel="preload" href="/assets/main/quds_ar/fonts/ibm-plex-sans-arabic/plex-arabic-600-core.woff2" as="font" type="font/woff2" crossorigin>
60            <link rel="preload" href="/assets/main/quds_ar/fonts/ibm-plex-sans-arabic/plex-arabic-700-core.woff2" as="font" type="font/woff2" crossorigin>
61            <link rel="preload" href="/assets/main/quds_ar/vendor/fontawesome/webfonts/fa-solid-900-subset.woff2" as="font" type="font/woff2" crossorigin>
62            <link rel="preload" href="/assets/main/quds_ar/vendor/fontawesome/webfonts/fa-regular-400-subset.woff2" as="font" type="font/woff2" crossorigin>
63    
64    
65    <link rel="stylesheet" data-fa-full data-href="/assets/main/quds_ar/vendor/fontawesome/css/all.min.css?v=1789735121">
66    <noscript><link rel="stylesheet" href="/assets/main/quds_ar/vendor/fontawesome/css/all.min.css?v=1789735121"></noscript>
67    <link rel="stylesheet" href="/assets/main/quds_ar/vendor/fontawesome/css/fa.subset.css?v=1789735121">
68    <link rel="stylesheet" href="/assets/main/quds_ar/vendor/swiper/swiper.subset.css?v=1789735121">
69    <link rel="stylesheet" href="/assets/main/quds_ar/vendor/aos/aos.subset.css?v=1789735121">
70
71    
72    <link rel="stylesheet" href="/assets/main/quds_ar/css/style.css?v=1789735121">
73            <link rel="stylesheet" href="/assets/main/quds_ar/css/pages.css?v=1787234408">
74    
75            
76        
76<script>
77            (function () {
78                var root = document.documentElement, t;
79                try { t = localStorage.getItem('qd_theme'); } catch (e) {}
80                if (t !== 'dark' && t !== 'light') {
81                    t = (window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
82                }
83                root.setAttribute('data-qtheme', t);
84            })();
85        </script>
85
86        <link rel="stylesheet" href="/assets/main/quds_ar/css/modes.css?v=1787676073">
87    
88    
89    <style>
90        .clamp-2, .clamp-3 { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
91        .clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
92        .clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }
93
94        /* live search suggestions (autocomplete dropdown) */
95        .search-suggest { margin-top: 16px; display: flex; flex-direction: column; gap: 4px; max-height: 360px; overflow-y: auto; }
96        .search-suggest__item { display: flex; flex-direction: column; gap: 3px; padding: 11px 14px; border-radius: 10px; border: 1px solid transparent; transition: .15s; cursor: pointer; }
97        .search-suggest__item:hover, .search-suggest__item.is-active { background: var(--bg-blue); border-color: var(--line); }
98        .search-suggest__cat { font-size: 11.5px; font-weight: 700; color: var(--accent); }
99        .search-suggest__cat i { margin-inline-end: 5px; }
100        .search-suggest__item--podcast .search-suggest__cat { color: #7c3aed; }
101        .search-suggest__item--video .search-suggest__cat { color: #dc2626; }
102        .search-suggest__item--tag .search-suggest__cat { color: var(--muted); }
103        .search-suggest__title { font-size: 14.5px; font-weight: 600; color: var(--ink); line-height: 1.55; }
104        .search-suggest__state { padding: 16px 6px; text-align: center; color: var(--muted); font-size: 14px; }
105        .search-suggest__state i { color: var(--accent); margin-inline-end: 6px; }
106
107        /* newsletter inline error */
108        .news-form__error { margin-top: 10px; color: #fff; background: rgba(220,53,69,.92); padding: 8px 14px; border-radius: 10px; font-size: 13.5px; font-weight: 600; }
109
110        /* most-read tabs (24h / week / all-time) */
111        [x-cloak] { display: none !important; }
112        .mr-tabs { display: flex; flex-wrap: wrap; gap: 8px; }
113        .mr-tab { font-size: 13px; font-weight: 700; color: var(--muted); background: var(--bg); border: 1px solid var(--line); padding: 7px 15px; border-radius: 30px; cursor: pointer; transition: .15s; font-family: inherit; }
114        .mr-tab:hover { color: var(--ink); border-color: var(--accent); }
115        .mr-tab.is-active { background: var(--accent); color: #fff; border-color: var(--accent); }
116        .mr-item { display: flex; align-items: center; gap: 12px; }
117
118        /* read-aloud (TTS) bar */
119        .tts-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 16px 0 6px; }
120        .tts-bar__play { display: inline-flex; align-items: center; gap: 9px; background: var(--accent); color: #fff; border: none; font-family: inherit; font-weight: 700; font-size: 14.5px; padding: 10px 20px; border-radius: 30px; cursor: pointer; transition: .15s; }
121        .tts-bar__play:hover { filter: brightness(1.07); }
122        .tts-bar__play.is-active { background: var(--accent-dark, #1f5fa8); }
123        .tts-bar__controls { display: inline-flex; align-items: center; gap: 6px; }
124        .tts-bar__btn { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--line); background: var(--bg); color: var(--ink); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
125        .tts-bar__btn:hover { border-color: var(--accent); color: var(--accent); }
126        .tts-bar__sep { width: 1px; height: 22px; background: var(--line); margin: 0 4px; }
127        .tts-bar__rate { border: 1px solid var(--line); background: var(--bg); color: var(--muted); font-family: inherit; font-weight: 700; font-size: 12.5px; padding: 6px 11px; border-radius: 20px; cursor: pointer; }
128        .tts-bar__rate.is-active { background: var(--accent); color: #fff; border-color: var(--accent); }
129
130        /* ===== audio news digest «موجز الأخبار» ===== */
131        .newsbrief { display: grid; grid-template-columns: 1fr; border-radius: 20px; overflow: hidden; box-shadow: var(--shadow-card, 0 18px 44px rgba(15, 35, 60, .14)); border: 1px solid var(--line); background: var(--bg); }
132        @media (min-width: 900px) { .newsbrief { grid-template-columns: minmax(330px, 38%) 1fr; } }
133        .newsbrief__player { position: relative; overflow: hidden; padding: 30px 28px; color: #fff; background: linear-gradient(145deg, #0f2a48 0%, var(--accent-dark, #1c4a70) 55%, var(--accent, #2f7fcf) 150%); display: flex; flex-direction: column; }
134        .newsbrief__blob { position: absolute; width: 240px; height: 240px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,.16), transparent 65%); inset-block-start: -90px;
134 inset-inline-start: -70px; pointer-events: none; }
135        .newsbrief__badge { position: relative; align-self: flex-start; display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 800; letter-spacing: .3px; background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.28); padding: 6px 14px; border-radius: 30px; }
136        .newsbrief__title { position: relative; font-size: 28px; font-weight: 800; margin: 16px 0 6px; }
137        .newsbrief__sub { position: relative; font-size: 13.5px; color: #d6e6f6; margin-bottom: 22px; }
138        .newsbrief__controls { position: relative; margin-top: auto; display: flex; align-items: center; gap: 14px; }
139        .newsbrief__play { flex: none; width: 58px; height: 58px; border-radius: 50%; border: none; cursor: pointer; background: var(--bg); color: var(--accent-dark, #1f5fa8); font-size: 20px; box-shadow: 0 8px 22px rgba(0,0,0,.25); transition: transform .15s; display: inline-flex; align-items: center; justify-content: center; }
140        .newsbrief__play:hover { transform: scale(1.06); }
141        .newsbrief__eq { display: inline-flex; align-items: flex-end; gap: 3px; height: 24px; }
142        .newsbrief__eq span { width: 3px; height: 7px; background: rgba(255,255,255,.55); border-radius: 2px; }
143        .newsbrief__player.is-playing .newsbrief__eq span { animation: nbEq .9s ease-in-out infinite; }
144        .newsbrief__player.is-playing .newsbrief__eq span:nth-child(2) { animation-delay: .12s; }
145        .newsbrief__player.is-playing .newsbrief__eq span:nth-child(3) { animation-delay: .24s; }
146        .newsbrief__player.is-playing .newsbrief__eq span:nth-child(4) { animation-delay: .36s; }
147        .newsbrief__player.is-playing .newsbrief__eq span:nth-child(5) { animation-delay: .48s; }
148        @keyframes nbEq { 0%, 100% { height: 7px; } 50% { height: 22px; } }
149        .newsbrief__track { flex: 1; height: 6px; border-radius: 6px; background: rgba(255,255,255,.22); overflow: hidden; }
150        .newsbrief__progress { width: 0; height: 100%; background: var(--bg); border-radius: 6px; transition: width .25s linear; }
151        .newsbrief__time { font-size: 12.5px; font-weight: 700; color: #d6e6f6; min-width: 38px; }
152        .newsbrief__list { margin: 0; padding: 12px 10px; list-style: none; display: flex; flex-direction: column; }
153        .newsbrief__list li + li { border-top: 1px solid var(--line); }
154        .newsbrief__list a { display: flex; align-items: center; gap: 14px; padding: 13px 14px; border-radius: 12px; transition: background .15s; }
155        .newsbrief__list a:hover { background: var(--bg-blue, #f3f7fb); }
156        .newsbrief__num { flex: none; font-size: 13px; font-weight: 800; color: var(--accent); width: 26px; }
157        .newsbrief__headline { flex: 1; font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.55; }
158        .newsbrief__go { color: var(--muted-3, #a4a9b2); font-size: 14px; }
159        .newsbrief__list a:hover .newsbrief__go { color: var(--accent); }
160    </style>
161
162    
163    <!-- Global site tag (gtag.js) - Google Analytics -->
vendor: 96 bytes, lines 163-164
163
164<script type="text/devlo-deferred" async data-src="https://www.googletagmanager.com/gtag/js?id=
164UA-38391677-1
vendor: 12 bytes, line 164
164"></script>
165<script>
166  
vendor: 134 bytes, lines 166-170
166window.dataLayer = window.dataLayer || [];
167  function gtag(){dataLayer.push(arguments);}
168  gtag('js', new Date());
169
170  gtag('config', '
170UA-38391677-1
vendor: 4 bytes, line 170
170');
171</script>
171
172
173<script type="text/devlo-deferred" async data-src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8563461161168276"
174     crossorigin="anonymous"></script>
174
175
176<script>function coronavirus(){var TotalCases=JSON.parse(this.responseText);document.querySelector("#TotalCases").innerHTML=TotalCases.data.TotalCases;var TotalActiveCases=JSON.parse(this.responseText);document.querySelector("#TotalActiveCases").innerHTML=TotalActiveCases.data.TotalActiveCases;var TotalRecovery=JSON.parse(this.responseText);document.querySelector("#TotalRecovery").innerHTML=TotalRecovery.data.TotalRecovery;var TotalCriticalCases=JSON.parse(this.responseText);document.querySelector("#TotalCriticalCases").innerHTML=TotalCriticalCases.data.TotalCriticalCases;var TotalDeath=JSON.parse(this.responseText);document.querySelector("#TotalDeath").innerHTML=TotalDeath.data.TotalDeath;} var coronavirusupdate=new XMLHttpRequest();coronavirusupdate.addEventListener("load",coronavirus);coronavirusupdate.open("GET","https://corona.ps/API/summary");coronavirusupdate.send();</script>
176
177
178<meta name="google-adsense-account" content="ca-pub-8563461161168276">
179<script>
180/* Replays the third-party 
180<script src> tags above once the page has loaded.
181   Order is preserved; async/defer/crossorigin and every data-* attribute other
182   than data-src are carried over untouched. */
183(function () {
184    if (window.__devloDeferredCode) { return; }
185    window.__devloDeferredCode = true;
186
187    function idle(fn, timeout) {
188        if (window.requestIdleCallback) requestIdleCallback(fn, { timeout: timeout || 2000 });
189        else setTimeout(fn, 0);
190    }
191
192    /*
193     * ONE AT A TIME, each in its own idle slot.
194     *
195     * Releasing them together only moves the pile-up: analytics and the ad
196     * stack then boot in the same burst and hold the main thread through it,
197     * which is exactly the stall this defer exists to avoid — just later in the
198     * page's life, where it lands on a reader who has started scrolling.
199     * Waiting for each to finish before asking for the next slot leaves gaps
200     * the browser can spend on input instead.
201     *
202     * Errors and blocked requests (an ad blocker, an offline visitor) resolve
203     * the same way a load does, so one refused script can never strand the
204     * ones behind it.
205     */
206    function next(tags, i) {
207        if (i >= tags.length) { return; }
208
209        var old = tags[i];
210        var s = document.createElement('script');
211        for (var a = 0; a < old.attributes.length; a++) {
212            var at = old.attributes[a];
213            if (at.name === 'type' || at.name === 'data-src') { continue; }
214            s.setAttribute(at.name, at.value);
215        }
216
217        var moved = false;
218        function advance() {
219            if (moved) { return; }
220            moved = true;
221            idle(function () { next(tags, i + 1); }, 1000);
222        }
223        s.addEventListener('load', advance, { once: true });
224        s.addEventListener('error', advance, { once: true });
225        // A script that never fires either event must not strand the queue.
226        setTimeout(advance, 4000);
227
228        s.src = old.getAttribute('data-src');
229        old.parentNode.insertBefore(s, old);
230        old.parentNode.removeChild(old);
231    }
232
233    function run() {
234        next(document.querySelectorAll('script[type="text/devlo-deferred"]'), 0);
235    }
236
237    /*
238     * "After load" stopped being late enough.
239     *
240     * Once the page got lighter the load event moved from ~1.2s to ~0.45s, so
241     * releasing on load put the ad stack back on the main thread WHILE the
242     * article was still painting — the exact contention this defer exists to
243     * avoid, just reached sooner. Wait for the largest contentful paint to
244     * settle instead: that is the frame the reader came for, and it is the
245     * honest definition of "the page is ready for someone else's code".
246     *
247     * The observer reports a new candidate each time a bigger element paints,
248     * so the signal is not the first entry but a quiet gap after one. A 6s cap
249     * from load runs them regardless — on a browser with no LCP support, a page
250     * that never produces a candidate, or a reader who just sits still.
251     */
252    function whenContentSettled(cb) {
253        var fired = false;
254        function go() {
255            if (fired) { return; }
256            fired = true;
257            idle(cb, 2000);
258        }
259
260        setTimeout(go, 6000);
261
262        try {
263            var quiet = null;
264            var po = new PerformanceObserver(function () {
265                clearTimeout(quiet);
266                quiet = setTimeout(function () {
267                    try { po.disconnect(); } catch (e) { /* already gone */ }
268                    go();
269                }, 700);
270            });
271            po.observe({ type: 'largest-contentful-paint', buffered: true });
272        } catch (e) {
273            // No LCP observer here: fall back to a short settle after load.
274            setTimeout(go, 1500);
275        }
276    }
277
278    if (document.readyState === 'complete') { whenContentSettled(run); }
279    else { window.addEventListener('load', function () { whenContentSettled(run); }, { once: true }); }
280})();
281</script>
281
282        <style>
283        /* page-scoped helpers for the "تواصل معنا" page only */
284        .contact-social{margin-top:4px;padding-top:18px;border-top:1px solid var(--line)}
285        .contact-social__label{display:block;font-size:13px;font-weight:700;color:var(--muted);margin-bottom:12px}
286        .contact-social__row{display:flex;flex-wrap:wrap;gap:8px}
287    </style>
288        
288<script>
289    (function () {
290        // Relative on purpose: this HTML gets written to disk and replayed, so a
291        // baked absolute URL would carry whatever scheme/host the seeding render
292        // computed — an http:// APP_URL behind Cloudflare TLS turns the refresh
293        // into blocked mixed content on every visitor.
294        var ENDPOINT = '\/__csrf';
295
296        /*
297         * Livewire's getCsrfToken() tries meta[name="csrf-token"], then
298         * [data-csrf], then window.livewireScriptConfig.csrf, and takes the
299         * first that exists. Only four themes emit the meta tag; on the rest the
300         * live value is the data-csrf attribute Livewire puts on its own script
301         * tag, so patching the meta alone would fix nothing there. Read and
302         * write all of them, in that same order.
303         */
304        function currentToken() {
305            var tag = document.querySelector('meta[name="csrf-token"]');
306            if (tag) {
307                return tag.getAttribute('content');
308            }
309            var holder = document.querySelector('[data-csrf]');
310            if (holder) {
311                return holder.getAttribute('data-csrf');
312            }
313            return (window.livewireScriptConfig && window.livewireScriptConfig.csrf) || null;
314        }
315
316        function apply(token) {
317            var i;
318            var metas = document.querySelectorAll('meta[name="csrf-token"]');
319            for (i = 0; i < metas.length; i++) {
320                metas[i].setAttribute('content', token);
321            }
322            var holders = document.querySelectorAll('[data-csrf]');
323            for (i = 0; i < holders.length; i++) {
324                holders[i].setAttribute('data-csrf', token);
325            }
326            // Plain form POSTs (the newsletter) carry their own baked copy.
327            var fields = document.querySelectorAll('input[name="_token"]');
328            for (i = 0; i < fields.length; i++) {
329                fields[i].value = token;
330            }
331            if (window.livewireScriptConfig) {
332                window.livewireScriptConfig.csrf = token;
333            }
334        }
335
336        // The token the server last confirmed is live for this visitor's session.
337        // Survives wire:navigate: this 
337<script> is byte-identical on every page,
338        // and Livewire's mergeNewHead skips head assets it already has, so this
339        // closure is never re-created and no listener is ever bound twice.
340        var known = null;
341        var pending = null;
342
343        /**
344         * Resolves with the live token, or null. One request at a time: a
345         * caller that arrives while one is in the air waits on the same one.
346         */
347        function refresh() {
348            if (pending) {
349                return pending;
350            }
351
352            try {
353                pending = fetch(ENDPOINT, {
354                    headers: { 'Accept': 'application/json' },
355                    credentials: 'same-origin',
356                    // This runs while the <head> is still parsing, so without a
357                    // hint the browser schedules it alongside the stylesheets
358                    // and the LCP image. Nothing waits on the token — it only
359                    // has to land before the first interaction — so let the
360                    // render-critical requests go first. Ignored where Priority
361                    // Hints are unsupported, which just restores the old timing.
362                    priority: 'low',
363                })
364                    .then(function (r) { return r.ok ? r.json() : null; })
365                    .then(function (data) {
366                        pending = null;
367                        if (!data || !data.token) {
368                            return null;
369                        }
370                        known = data.token;
371                        apply(known);
372                        // This runs from <head>, and both the _token fields and
373                        // Livewire's data-csrf script tag live further down the
374                        // page — they may not exist yet when the fetch lands.
375                        if (document.readyState === 'loading') {
376                            document.addEventListener('DOMContentLoaded', function () {
377                                apply(known);
378                            }, { once: true });
379                        }
380
381                        return known;
382                    })
383                    .catch(function () { pending = null; return null; });
384            } catch (e) {
385                // No fetch (ancient browser) — leave the baked token alone.
386                pending = null;
387
388                return Promise.resolve(null);
389            }
390
391            return pending;
392        }
393
394        function hasXsrfCookie() {
395            return /(^|;\s*)XSRF-TOKEN=/.test(document.cookie);
396        }
397
398        /*
399         * Two signals now, because the cookie alone was wrong for the visitor
400         * who matters most here.
401         *
402         * No cookie still means «this HTML came off disk», and it is known
403         * while the <head> is still parsing — so that fetch goes out as early
404         * as it always did.
405         *
406         * The second signal is the page's own token, and it is the exact one:
407         * a cached copy carries NO token at all, since PageCsrfToken::strip
408         * empties every holder before the file is written. A reader who
409         * already held an XSRF-TOKEN from an earlier live page used to be
410         * taken for a live render and left with the token the file was seeded
411         * with, so their next click answered 419 — «This page has expired», a
412         * maktoob reader on the archive's pagination, 2026-09-22. Read after
413         * DOMContentLoaded on purpose: on the themes that emit no meta tag the
414         * live value is Livewire's data-csrf attribute, and that script tag is
415         * at the end of <body>, nowhere near this one.
416         */
417        if (!hasXsrfCookie()) {
418            refresh();
419        } else if (document.readyState === 'loading') {
420            document.addEventListener('DOMContentLoaded', adopt, { once: true });
421        } else {
422            adopt();
423        }
424
425        /** This page's own token, when it has one: it IS the session's. */
426        function adopt() {
427            var baked = currentToken();
428
429            if (baked) {
430                // Recording it costs nothing and gives the navigation check
431                // below a baseline.
432                known = baked;
433            } else {
434                watchForIntent();
435            }
436        }
437
438        /*
439         * A cached page in the hands of a visitor who already has a session.
440         * Asking for the token on load would put one request — and one row in
441         * a `sessions` table that reached 8.6M on maktoob — behind every view
442         * of the most-visited pages on the site, which is the cost these
443         * caches exist to remove. So the token is fetched the moment the
444         * visitor reaches for something that will need it, and a reader who
445         * only reads never asks at all. A click that lands before the answer
446         * does is what the 419 net below is for.
447         */
448        var watching = false;
449
450        function watchForIntent() {
451            if (watching) {
452                return;
453            }
454            watching = true;
455
456            ['pointerdown', 'focusin', 'keydown'].forEach(function (name) {
457                document.addEventListener(name, onIntent, true);
458            });
459        }
460
461        function onIntent(event) {
462            if (!event.target || !needsToken(event.target)) {
463                return;
464            }
465
466            ['pointerdown', 'focusin', 'keydown'].forEach(function (name) {
467                document.removeEventListener(name, onIntent, true);
468            });
469            watching = false;
470
471            refresh();
472        }
473
474        /** A `wire:` control or a form — anything else needs no token. */
475        function needsToken(target) {
476            for (var el = target; el && el.nodeType === 1; el = el.parentElement) {
477                if (el.tagName === 'FORM') {
478                    return true;
479                }
480
481                for (var i = 0; i < el.attributes.length; i++) {
482                    if (el.attributes[i].name.indexOf('wire:') === 0) {
483                        return true;
484                    }
485                }
486            }
487
488            return false;
489        }
490
491        /*
492         * wire:navigate does not preserve the fix. mergeNewHead() treats only
493         * 
493<script>/<style>/stylesheet <link> as assets and deletes every other
494         * head child, so a patched meta is dropped for the incoming page's own —
495         * and the body, data-csrf script tag included, is swapped wholesale.
496         * Whenever that incoming page is itself a cache HIT the token is stale
497         * again. But the session's token does not change between navigations, so
498         * a token we already fetched can simply be re-applied — instant, and one
499         * request for the whole visit instead of one per cached page. Only a
500         * visitor who has never held a live token asks the server.
501         */
502        document.addEventListener('livewire:navigated', function () {
503            var now = currentToken();
504
505            if (!known) {
506                // Nothing held yet: the incoming page's own token, if it has
507                // one, is the session's; a cached one has none and must ask.
508                if (now) {
509                    known = now;
510                } else if (hasXsrfCookie()) {
511                    watchForIntent();
512                } else {
513                    refresh();
514                }
515
516                return;
517            }
518
519            if (now !== known) {
520                apply(known);
521            }
522        });
523
524        /*
525         * The net under all of it.
526         *
527         * Whatever is repaired above, a token can still be wrong when the
528         * request goes out: a tab left open until `sessions:prune` removed its
529         * row, a cached file written before this shipped, a click that landed
530         * while the fetch was still in the air. Livewire answers a 419 with a
531         * native confirm() — «This page has expired. Would you like to refresh
532         * the page?» — which blocks the JS thread, so whatever the reader
533         * pressed also stays frozen behind the dialog.
534         *
535         * So the dialog is suppressed, a fresh token fetched, and the action
536         * that failed sent once more. The 419 stopped the request at the CSRF
537         * middleware, before the component ran, so nothing happened twice.
538         */
539        var recoveries = 0;
540        var hooked = false;
541
542        function hookLivewire() {
543            if (hooked || !window.Livewire || !window.Livewire.hook) {
544                return;
545            }
546            hooked = true;
547
548            var sent = [];
549
550            window.Livewire.hook('commit', function (context) {
551                var entry = { component: context.component, commit: context.commit, done: false };
552
553                sent.push(entry);
554                if (sent.length > 10) {
555                    sent.shift();
556                }
557
558                context.succeed(function () { entry.done = true; });
559            });
560
561            window.Livewire.hook('request', function (context) {
562                context.fail(function (failure) {
563                    if (failure.status !== 419) {
564                        return;
565                    }
566
567                    // No confirm(); the request's finish hooks then run as
568                    // usual, so wire:loading states release.
569                    failure.preventDefault();
570
571                    // Twice, so a token that is somehow wrong again stops
572                    // instead of looping.
573                    if (recoveries >= 2) {
574                        return;
575                    }
576                    recoveries++;
577
578                    var again = sent.filter(function (entry) { return !entry.done; });
579                    sent = [];
580
581                    known = null;
582
583                    refresh().then(function (token) {
584                        if (token) {
585                            again.forEach(replay);
586                        }
587                    });
588                });
589            });
590        }
591
592        /** Send a failed commit again, now that the page holds a live token. */
593        function replay(entry) {
594            var wire = entry.component && entry.component.$wire;
595            if (!wire) {
596                return;
597            }
598
599            var updates = entry.commit.updates || {};
600            var calls = (entry.commit.calls || []).filter(function (call) {
601                // `$set` IS the updates below — replaying it as a call would
602                // send the magic method with no arguments.
603                return call.method && call.method !== '$set';
604            });
605
606            // Everything fired in this tick coalesces into one request.
607            Object.keys(updates).forEach(function (name) {
608                try { wire.set(name, updates[name]); } catch (e) {}
609            });
610
611            calls.forEach(function (call) {
612                try { wire.call.apply(wire, [call.method].concat(call.params || [])); } catch (e) {}
613            });
614        }
615
616        document.addEventListener('livewire:init', hookLivewire);
617        hookLivewire();
618    })();
619</script>
619
620
621
622    
622<script>
623    (function () {
624        var URL = '\/__view';
625        var ARMED = [];
626        var PAGE = JSON.parse('{\u0022k\u0022:\u0022page\u0022,\u0022p\u0022:\u0022\\\/contact_us\u0022,\u0022s\u0022:\u00224600845e3762d1edd3b2c4410d27aa2c\u0022}');
627        var sent = {};
628        var pending = {};
629        var pageTimers = [];
630
631        function enc(v) { return encodeURIComponent(v); }
632
633        function send(body) {
634            try {
635                if (navigator.sendBeacon && navigator.sendBeacon(URL, new Blob([body], {type: 'application/x-www-form-urlencoded'}))) return;
636            } catch (e) {}
637            try {
638                fetch(URL, {
639                    method: 'POST',
640                    body: body,
641                    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
642                    credentials: 'same-origin',
643                    keepalive: true
644                }).catch(function () {});
645            } catch (e) {}
646        }
647
648        // `phase`: 'o' = opened it, 'r' = stayed on it.
649        function itemBody(p, phase) {
650            return 't=' + enc(p.t) + '&i=' + enc(p.i) + '&s=' + enc(p.s) + '&p=' + phase;
651        }
652        function pageBody() {
653            if (!PAGE) return '';
654            return 'k=' + enc(PAGE.k) + '&pp=' + enc(PAGE.p) + '&ps=' + enc(PAGE.s)
655                + '&tt=' + enc((document.title || '').slice(0, 180));
656        }
657
658        function post(p, phase) {
659            var key = phase + ':' + p.t + ':' + p.i;
660            if (sent[key]) return;
661            sent[key] = 1;
662            send(itemBody(p, phase));
663        }
664
665        // Visible-time timer: accumulates only while `wanted` AND the tab is
666        // visible; calls done() once `need` ms have been reached. need = 0 is
667        // "as soon as the page is really on screen", which is what an open is.
668        function timer(need, done) {
669            var acc = 0, since = null, handle = null, dead = false, wanted = true;
670            function elapsed() { return acc + (since === null ? 0 : Date.now() - since); }
671            function check() {
672                if (dead) return;
673                if (elapsed() >= need) { dead = true; clearTimeout(handle); done(); }
674                else schedule();
675            }
676            function schedule() { clearTimeout(handle); handle = setTimeout(check, Math.max(50, need - elapsed())); }
677            function sync() {
678                if (dead) return;
679                var run = wanted && !document.hidden;
680                if (run && since === null) { since = Date.now(); schedule(); }
681                else if (!run && since !== null) { acc += Date.now() - since; since = null; clearTimeout(handle); }
682            }
683            document.addEventListener('visibilitychange', sync);
684            sync();
685            return {
686                pause: function () { wanted = false; sync(); },
687                resume: function () { wanted = true; sync(); },
688                cancel: function () { dead = true; clearTimeout(handle); }
689            };
690        }
691
692        // ── The load report: the page, plus the open of what it shows. One
693        //    request when both exist — an article page must not cost more
694        //    round-trips than it did before the page stat existed.
695        pageTimers.push(timer(0, function () {
696            var page = pageBody();
697            var first = ARMED.length ? ARMED[0] : null;
698
699            if (first) {
700                sent['o:' + first.t + ':' + first.i] = 1;
701                send(itemBody(first, 'o') + (page ? '&' + page : ''));
702            } else if (page) {
703                send(page);
704            }
705
706            for (var i = 1; i < ARMED.length; i++) post(ARMED[i], 'o');
707        }));
708
709        // ── The read report: one per armed item, after its dwell.
710        ARMED.forEach(function (p) {
711            pageTimers.push(timer((p.d || 0) * 1000, function () { post(p, 'r'); }));
712        });
713
714        function hitEl(target) {
715            return target && target.closest ? target.closest('[data-view-hit]') : null;
716        }
717        function payloadOf(el) {
718            try { return JSON.parse(el.getAttribute('data-view-hit')); } catch (e) { return null; }
719        }
720        function arm(el, media) {
721            var p = payloadOf(el);
722            if (!p) return;
723            var key = p.t + ':' + p.i;
724            if (sent['r:' + key]) return;
725            // Touching it IS opening it — the dwell below decides the read.
726            post(p, 'o');
727            if (pending[key]) { pending[key].resume(); return; }
728            var need = (p.d || 0) * 1000;
729            // A clip shorter than the dwell counts at 80% of its own length.
730            if (media && isFinite(media.duration) && media.duration > 0) {
731                need = Math.min(need, media.duration * 800);
732            }
733            pending[key] = timer(need, function () { delete pending[key]; post(p, 'r'); });
734        }
735
736        // Capture phase: a theme handler that stops propagation (a deck or a
737        // modal opener) must not be able to hide the click from the counter.
738        document.addEventListener('click', function (e) {
739            var el = hitEl(e.target);
740            if (el && (el.getAttribute('data-view-on') || 'click') === 'click') arm(el, null);
741        }, true);
742        // A click INSIDE an embedded player (YouTube / Instagram iframe) never
743        // reaches this document; the focus moving into the iframe does.
744        window.addEventListener('blur', function () {
745            setTimeout(function () {
746                var a = document.activeElement;
747                if (!a || a.tagName !== 'IFRAME') return;
748                var el = hitEl(a);
749                if (el && (el.getAttribute('data-view-on') || 'click') === 'click') arm(el, null);
750            }, 0);
751        });
752        // Media events do not bubble — capture them.
753        document.addEventListener('play', function (e) {
754            var el = hitEl(e.target);
755            if (el && el.getAttribute('data-view-on') === 'play') arm(el, e.target);
756        }, true);
757        document.addEventListener('pause', function (e) {
758            var el = hitEl(e.target), p = el && payloadOf(el);
759            if (p && pending[p.t + ':' + p.i]) pending[p.t + ':' + p.i].pause();
760        }, true);
761
762        document.addEventListener('livewire:navigating', function () {
763            pageTimers.forEach(function (t) { t.cancel(); });
764            for (var k in pending) pending[k].cancel();
765            pending = {};
766        }, {once: true});
767    })();
768</script>
768
769
770
771<style>
772    /* !important so the clamp wins even when a theme rule (e.g. a compound
773       selector like `.card--featured .excerpt{display:block}`) has higher
774       specificity than this single-class utility. Only ever applied to text
775       containers (titles/excerpts), so forcing the display is safe. */
776    .line-clamp-1,.line-clamp-2,.line-clamp-3,.line-clamp-4{display:-webkit-box!important;-webkit-box-orient:vertical!important;overflow:hidden!important}
777    .line-clamp-1{-webkit-line-clamp:1!important}
778    .line-clamp-2{-webkit-line-clamp:2!important}
779    .line-clamp-3{-webkit-line-clamp:3!important}
780    .line-clamp-4{-webkit-line-clamp:4!important}
781</style>
782<meta name="generator" content="Devlo CMS"/>
783<!-- Livewire Styles --><style >[wire\:loading][wire\:loading], [wire\:loading\.delay][wire\:loading\.delay], [wire\:loading\.list-item][wire\:loading\.list-item], [wire\:loading\.inline-block][wire\:loading\.inline-block], [wire\:loading\.inline][wire\:loading\.inline], [wire\:loading\.block][wire\:loading\.block], [wire\:loading\.flex][wire\:loading\.flex], [wire\:loading\.table][wire\:loading\.table], [wire\:loading\.grid][wire\:loading\.grid], [wire\:loading\.inline-flex][wire\:loading\.inline-flex] {display: none;}[wire\:loading\.delay\.none][wire\:loading\.delay\.none], [wire\:loading\.delay\.shortest][wire\:loading\.delay\.shortest], [wire\:loading\.delay\.shorter][wire\:loading\.delay\.shorter], [wire\:loading\.delay\.short][wire\:loading\.delay\.short], [wire\:loading\.delay\.default][wire\:loading\.delay\.default], [wire\:loading\.delay\.long][wire\:loading\.delay\.long], [wire\:loading\.delay\.longer][wire\:loading\.delay\.longer], [wire\:loading\.delay\.longest][wire\:loading\.delay\.longest] {display: none;}[wire\:offline][wire\:offline] {display: none;}[wire\:dirty]:not(textarea):not(input):not(select) {display: none;}:root {--livewire-progress-bar-color: #2299dd;}[x-cloak] {display: none !important;}[wire\:cloak] {display: none !important;}dialog#livewire-error::backdrop {background-color: rgba(0, 0, 0, .6);}</style>
784</head>
785
786<body data-page="">
787
788<div class="topbar">
789    <div class="container topbar__inner">
790        <div class="topbar__meta">
791            <span><i class="fa-regular fa-calendar" aria-hidden="true"></i>
792                الجمعة، 25 سبتمبر 2026</span>
793        </div>
794        <div class="topbar__social">
795                            <a href="https://tiktok.com/@qudsn" target="_blank" rel="noopener" aria-label="Tiktok" class="social-link"><i class="fab fa-tiktok" aria-hidden="true"></i></a>
796                            <a href="https://t.me/QudsN" target="_blank" rel="noopener" aria-label="Facebook" class="social-link"><i class="fab fa-facebook-f" aria-hidden="true"></i></a>
797                            <a href="https://www.instagram.com/qudsn" target="_blank" rel="noopener" aria-label="Instagram" class="social-link"><i class="fab fa-instagram" aria-hidden="true"></i></a>
798                            <a href="https://www.youtube.com/user/QudsNetwork" target="_blank" rel="noopener" aria-label="Youtube-play" class="social-link"><i class="fab fa-youtube" aria-hidden="true"></i></a>
799                            <a href="https://t.me/QudsN" target="_blank" rel="noopener" aria-label="قناة تيليغرام" class="social-link"><i class="fab fa-telegram" aria-hidden="true"></i></a>
800                            <a href="https://whatsapp.com/channel/0029VbCpgXo8fewqkgDTLM2z" target="_blank" rel="noopener" aria-label="قناة واتساب" class="social-link"><i class="fab fa-whatsapp" aria-hidden="true"></i></a>
801                            <a href="https://twitter.com/QudsN" target="_blank" rel="noopener" aria-label="Twitter" class="social-link"><i class="fab fa-x-twitter" aria-hidden="true"></i></a>
802                            <a href="https://t.me/Qudsn_hebrew" target="_blank" rel="noopener" aria-label="Telegram " class="social-link"><i class="fab fa-telegram" aria-hidden="true"></i></a>
803                        <span class="topbar__sep" aria-hidden="true"></span>
804            <a href="https://qudsnen.com" class="topbar__lang" target="_blank" rel="noopener">English</a>
805        </div>
806    </div>
807</div>
808
809
810<header class="site-header">
811    <div class="container header__inner">
812        <a href="https://qudsn.co" class="brand"
813            aria-label="شبكة قدس الإخبارية — الصفحة الرئيسية">
814            
815                        <img src="https://fsn1.your-objectstorage.com/qudsn/uploads/2026/08/file_6a6f0b281cbc8.png" alt="شبكة قدس الإخبارية" class="brand__logo" width="90" height="38"
816                decoding="async" >
817        </a>
818
819        
820        <nav class="cat-nav" id="catNav" aria-label="أقسام الموقع">
821            <div class="cat-nav__inner">
822                                    <a href="/"
823                                                class="cat-link">الرئيسية</a>
824                                    <a href="/category_news/أخبار"
825                                                class="cat-link">أخبار</a>
826                                    <a href="/category_news/متابعات قدس"
827                                                class="cat-link">متابعات</a>
828                                    <a href="/category_news/تقارير قدس"
829                                                class="cat-link">تقارير</a>
830                                    <a href="/category_news/مدوّنات"
831                                                class="cat-link">مدوّنات</a>
832                                    <a href="/category_news/ترجمات عبرية"
833                                                class="cat-link">ترجمات</a>
834                                    <a href="/all_podcast"
835                                                class="cat-link">بودكاست قدس</a>
836                                    <a href="/contact_us"
837                                                class="cat-link">اتصل بنا</a>
838                                    <a href="/special_page/3"
839                                                class="cat-link">من نحن</a>
840                
841            </div>
842
843            
844            <div class="cat-more" id="catMore" hidden>
845                <button type="button" class="cat-link cat-more__btn" aria-haspopup="true" aria-expanded="false">
846                    المزيد <i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
847                </button>
848                <div class="cat-more__menu" role="menu" hidden></div>
849            </div>
850        </nav>
851
852        <div class="header__actions">
853                            
854                <button type="button" class="icon-btn qd-theme-toggle" id="qdThemeToggle"
855                    aria-label="تبديل الوضع الليلي والنهاري" aria-pressed="false">
856                    
857                    <span class="theme-icon" aria-hidden="true">
858                        <svg class="theme-icon__moon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
859                            stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
860                            <path d="M20.4 13.2A8.2 8.2 0 1 1 10.8 3.6a6.6 6.6 0 0 0 9.6 9.6z"/>
861                        </svg>
862                        <svg class="theme-icon__sun" viewBox="0 0 24 24" fill="none" stroke="currentColor"
863                            stroke-width="1.9" stroke-linecap="round">
864                            <circle cx="12" cy="12" r="4"/>
865                            <path d="M12 2.6v2.1M12 19.3v2.1M2.6 12h2.1M19.3 12h2.1M5.4 5.4l1.5 1.5M17.1 17.1l1.5 1.5M18.6 5.4l-1.5 1.5M6.9 17.1l-1.5 1.5"/>
866                        </svg>
867                    </span>
868                </button>
869                        <button type="button" class="icon-btn" id="searchOpen" aria-label="فتح البحث" aria-haspopup="dialog">
870                <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
871            </button>
872            
873            <a href="https://t.me/QudsN" class="btn btn--primary" target="_blank" rel="noopener" aria-label="اشترك في القناة">
874                <i class="fa-regular fa-paper-plane" aria-hidden="true"></i><span>اشترك في القناة</span>
875            </a>
876            <button type="button" class="icon-btn nav-toggle" id="navToggle" aria-label="فتح القائمة"
877                aria-expanded="false" aria-controls="catNav">
878                <i class="fa-solid fa-bars" aria-hidden="true"></i>
879            </button>
880        </div>
881    </div>
882
883</header>
884
885
886<script>
887/* ==========================================================================
888   NAV OVERFLOW — move whatever will not fit into the «المزيد» dropdown
889   The menu is dashboard-driven, so no CSS breakpoint can know when it stops
890   fitting: the row is measured instead. Links keep their DOM order; only
891   their parent changes, so a link never disappears — worst case it is one
892   click deeper.
893   ========================================================================== */
894(function () {
895  'use strict';
896  var inner = document.querySelector('.header__inner .cat-nav__inner');
897  var more = document.getElementById('catMore');
898  if (!inner || !more) return;
899
900  var btn = more.querySelector('.cat-more__btn');
901  var menu = more.querySelector('.cat-more__menu');
902  // Snapshot the original order once: after the first pass the links live in
903  // two parents, and re-reading the row would lose the ones already moved.
904  var links = [].slice.call(inner.querySelectorAll(':scope > .cat-link'));
905  if (!links.length) return;
906
907  function close() {
908    if (menu.hidden) return;
909    menu.hidden = true;
910    btn.setAttribute('aria-expanded', 'false');
911  }
912
913  var bucketed = false;   // true once a pass has moved links into the menu
914
915  // Start from "everything fits" so the row can grow back when the window
916  // widens or a link is removed from the dashboard. Only when a previous pass
917  // actually moved something: re-appending every link on the first pass (all of
918  // them still in the row) dirtied the header's layout, and the measurement
919  // right after it then forced a synchronous layout of the whole page.
920  function restore() {
921    if (!bucketed) return;
922    links.forEach(function (l) { inner.appendChild(l); });
923    more.hidden = true;
924    bucketed = false;
925  }
926
927  function layout() {
928    // Below the burger breakpoint the drawer lists everything; putting links
929    // in the dropdown too would show them twice.
930    if (window.matchMedia('(max-width: 768px)').matches) {
931      restore();
932      close();
933      return;
934    }
935
936    close();
937    restore();
938
939    var fits = function () { return inner.scrollWidth <= inner.clientWidth + 1; };
940    if (fits()) return;               // it all fits — no bucket needed
941
942    // The button is a flex sibling of the row, so revealing it SHRINKS the
943    // row. Reveal it first and re-measure every pass — a width cached before
944    // this point is too generous and leaves one link half-cut.
945    more.hidden = false;
946
947    // Push links off the end until the row fits, prepending each one so the
948    // dropdown reads in the same order as the bar.
949    var moved = 0;
950    for (var i = links.length - 1; i >= 0 && !fits(); i--) {
951      menu.insertBefore(links[i], menu.firstChild);
952      moved++;
953    }
954
955    bucketed = true;
956    if (!moved) {                     // nothing moved: the bucket is dead weight
957      more.hidden = true;
958    }
959  }
960
961  btn.addEventListener('click', function (e) {
962    e.stopPropagation();
963    menu.hidden = !menu.hidden;
964    btn.setAttribute('aria-expanded', menu.hidden ? 'false' : 'true');
965  });
966  document.addEventListener('click', function (e) {
967    if (!more.contains(e.target)) close();
968  });
969  document.addEventListener('keydown', function (e) {
970    if (e.key === 'Escape') close();
971  });
972
973  layout();
974  // Fonts land after first paint and change every link's width, so a single
975  // pass at DOMContentLoaded measures the wrong row.
976  if (document.fonts && document.fonts.ready) {
977    document.fonts.ready.then(layout).catch(function () {});
978  }
979  var t;
980  window.addEventListener('resize', function () {
981    clearTimeout(t);
982    t = setTimeout(layout, 150);
983  });
984})();
985</script>
985
986
987
988
989<main id="main">
990    <div wire:snapshot="{&quot;data&quot;:{&quot;state&quot;:[{&quot;full_name&quot;:&quot;&quot;,&quot;email&quot;:&quot;&quot;,&quot;subject&quot;:&quot;&quot;,&quot;message&quot;:&quot;&quot;},{&quot;s&quot;:&quot;arr&quot;}]},&quot;memo&quot;:{&quot;id&quot;:&quot;RCTLk9ZOF5hYcn4kgdxh&quot;,&quot;name&quot;:&quot;main.quds-ar.send-news&quot;,&quot;path&quot;:&quot;contact_us&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;release&quot;:&quot;a-a-a&quot;,&quot;children&quot;:[],&quot;scripts&quot;:[],&quot;assets&quot;:[],&quot;errors&quot;:[],&quot;locale&quot;:&quot;ar&quot;,&quot;islands&quot;:[]},&quot;checksum&quot;:&quot;3c5432552dc73913508bc0d367d3709634130ceeeb9071d0c2bf1aa0d351c4aa&quot;}" wire:effects="[]" wire:id="RCTLk9ZOF5hYcn4kgdxh" wire:name="main.quds-ar.send-news">
991
992    <!-- breadcrumb -->
993    <nav class="breadcrumb" aria-label="مسار التصفّح">
994        <div class="container">
995            <ol class="breadcrumb__list">
996                <li><a href="https://qudsn.co">الرئيسية</a></li>
997                <li><i class="fa-solid fa-angle-left breadcrumb__sep" aria-hidden="true"></i></li>
998                <li class="breadcrumb__current">تواصل معنا</li>
999            </ol>
1000        </div>
1001    </nav>
1002
1003    <!-- ===== PAGE HERO ===== -->
1004    <section class="page-hero">
1005        <span class="page-hero__blob page-hero__blob--1" aria-hidden="true"></span>
1006        <span class="page-hero__blob page-hero__blob--2" aria-hidden="true"></span>
1007        <div class="container">
1008            <span class="page-hero__eyebrow"><i class="fa-regular fa-envelope" aria-hidden="true"></i>تواصل معنا</span>
1009            <h1 class="page-hero__title">تواصل معنا</h1>
1010            <p class="page-hero__sub">نسعد بتواصلكم واستقبال ملاحظاتكم واستفساراتكم على مدار الساعة.</p>
1011        </div>
1012    </section>
1013
1014    <!-- ===== CONTACT ===== -->
1015    <section class="section">
1016        <div class="container">
1017            
1018                        <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->            <div class="contact-layout">
1019
1020                <!-- INFO COLUMN -->
1021                <div class="contact-info" data-aos="fade-up">
1022                    <div class="contact-card">
1023                        <span class="contact-card__icon"><i class="fa-solid fa-envelope" aria-hidden="true"></i></span>
1024                        <div>
1025                            <div class="contact-card__label">البريد الإلكتروني</div>
1026                            <a class="contact-card__value" href="/cdn-cgi/l/email-protection#3950575f5679484c5d4a5717494a"><span class="__cf_email__" data-cfemail="99f0f7fff6d9e8ecfdeaf7b7e9ea">[email&#160;protected]</span></a>
1027                        </div>
1028                    </div>
1029
1030                    <div class="contact-card">
1031                        <span class="contact-card__icon"><i class="fa-solid fa-phone" aria-hidden="true"></i></span>
1032                        <div>
1033                            <div class="contact-card__label">الهاتف</div>
1034                            <a class="contact-card__value" href="tel:+970568644929">+970568644929</a>
1035                        </div>
1036                    </div>
1037
1038                    <div class="contact-card contact-card--value-rtl">
1039                        <span class="contact-card__icon"><i class="fa-solid fa-location-dot" aria-hidden="true"></i></span>
1040                        <div>
1041                            <div class="contact-card__label">المقر الرئيسي</div>
1042                            <div class="contact-card__value">رام الله</div>
1043                        </div>
1044                    </div>
1045
1046                    <div class="contact-social">
1047                        <span class="contact-social__label">تابعونا على منصّاتنا</span>
1048                        <div class="contact-social__row">
1049                            <a href="https://twitter.com/QudsN" aria-label="إكس" class="footer__social-link" target="_blank" rel="noopener"><i class="fa-brands fa-x-twitter" aria-hidden="true"></i></a>
1050                            <a href="https://www.instagram.com/qudsn" aria-label="إنستغرام" class="footer__social-link" target="_blank" rel="noopener"><i class="fa-brands fa-instagram" aria-hidden="true"></i></a>
1051                            <a href="https://t.me/QudsN" aria-label="تيليغرام" class="footer__social-link" target="_blank" rel="noopener"><i class="fa-brands fa-telegram" aria-hidden="true"></i></a>
1052                            <a href="https://whatsapp.com/channel/0029Va54z83IiRp163Clyw3P" aria-label="واتساب" class="footer__social-link" target="_blank" rel="noopener"><i class="fa-brands fa-whatsapp" aria-hidden="true"></i></a>
1053                            <a href="https://www.youtube.com/user/QudsNetwork" aria-label="يوتيوب" class="footer__social-link" target="_blank" rel="noopener"><i class="fa-brands fa-youtube" aria-hidden="true"></i></a>
1054                            <a href="https://tiktok.com/@qudsn" aria-label="تيك توك" class="footer__social-link" target="_blank" rel="noopener"><i class="fa-brands fa-tiktok" aria-hidden="true"></i></a>
1055                            <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                        </div>
1056                    </div>
1057                </div>
1058
1059                <!-- FORM COLUMN -->
1060                <div data-aos="fade-up" data-aos-delay="100">
1061                    <form class="form" id="contactForm" wire:submit.prevent="createSendNews" novalidate>
1062                        <div class="form__success" id="contactSuccess" wire:ignore.self
1063                             x-data="{ shown: false }"
1064                             x-on:hide_contact_form.window="shown = true; setTimeout(() => shown = false, 6000)"
1065                             x-show="shown" x-cloak :hidden="!shown">
1066                            <i class="fa-solid fa-circle-check" aria-hidden="true"></i>
1067                            <span>تم إرسال رسالتك بنجاح، سنردّ عليك قريبًا.</span>
1068                        </div>
1069
1070                        <div class="form__row">
1071                            <div class="field">
1072                                <label for="contactName">الاسم</label>
1073                                <input wire:model="state.full_name" id="contactName" name="name" type="text" autocomplete="name" placeholder="اكتب اسمك الكامل" required>
1074                                <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                            </div>
1075                            <div class="field">
1076                                <label for="contactEmail">البريد الإلكتروني</label>
1077                                <input wire:model="state.email" id="contactEmail" name="email" type="email" inputmode="email" autocomplete="email" placeholder="[email protected]" required>
1078                                <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                            </div>
1079                        </div>
1080
1081                        <div class="field">
1082                            <label for="contactSubject">الموضوع</label>
1083                            <input wire:model="state.subject" id="contactSubject" name="subject" type="text" placeholder="موضوع الرسالة">
1084                            <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                        </div>
1085
1086                        <div class="field">
1087                            <label for="contactMessage">الرسالة</label>
1088                            <textarea wire:model="state.message" id="contactMessage" name="message" placeholder="اكتب رسالتك هنا..." required></textarea>
1089                            <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                        </div>
1090
1091                        <button class="btn btn--primary" type="submit"><i class="fa-regular fa-paper-plane" aria-hidden="true"></i><span>إرسال الرسالة</span></button>
1092                    </form>
1093                </div>
1094
1095            </div>
1096        </div>
1097    </section>
1098
1099</div>
1100</main>
1101
1102<footer class="site-footer">
1103    <div class="container footer__grid">
1104        <div class="footer__col">
1105            <a href="https://qudsn.co" class="footer__brand" aria-label="شبكة قدس الإخبارية">
1106                                <img src="https://fsn1.your-objectstorage.com/qudsn/uploads/2026/08/file_6a6f0b28738ed.png" alt="شبكة قدس الإخبارية" loading="lazy" decoding="async" width="90" height="38"
1107                    >
1108            </a>
1109            <p class="footer__about">
1110                 نقدم المعالجات الإخبارية والتحليلات المعمقة المرتبطة بالشأن الفلسطيني وتأثيراته إقليميا ودوليا عبر قوالب تركز على المهنية
1111            </p>
1112            <div class="footer__social">
1113                                    <a href="https://tiktok.com/@qudsn" target="_blank" rel="noopener" aria-label="Tiktok" class="footer__social-link"><i class="fab fa-tiktok" aria-hidden="true"></i></a>
1114                                    <a href="https://t.me/QudsN" target="_blank" rel="noopener" aria-label="Facebook" class="footer__social-link"><i class="fab fa-facebook-f" aria-hidden="true"></i></a>
1115                                    <a href="https://www.instagram.com/qudsn" target="_blank" rel="noopener" aria-label="Instagram" class="footer__social-link"><i class="fab fa-instagram" aria-hidden="true"></i></a>
1116                                    <a href="https://www.youtube.com/user/QudsNetwork" target="_blank" rel="noopener" aria-label="Youtube-play" class="footer__social-link"><i class="fab fa-youtube" aria-hidden="true"></i></a>
1117                                    <a href="https://t.me/QudsN" target="_blank" rel="noopener" aria-label="قناة تيليغرام" class="footer__social-link"><i class="fab fa-telegram" aria-hidden="true"></i></a>
1118                                    <a href="https://whatsapp.com/channel/0029VbCpgXo8fewqkgDTLM2z" target="_blank" rel="noopener" aria-label="قناة واتساب" class="footer__social-link"><i class="fab fa-whatsapp" aria-hidden="true"></i></a>
1119                                    <a href="https://twitter.com/QudsN" target="_blank" rel="noopener" aria-label="Twitter" class="footer__social-link"><i class="fab fa-x-twitter" aria-hidden="true"></i></a>
1120                                    <a href="https://t.me/Qudsn_hebrew" target="_blank" rel="noopener" aria-label="Telegram " class="footer__social-link"><i class="fab fa-telegram" aria-hidden="true"></i></a>
1121                            </div>
1122        </div>
1123
1124        <nav class="footer__col" aria-label="الأقسام">
1125            <h3 class="footer__title">الأقسام</h3>
1126            <ul class="footer__links">
1127                                    <li><a
1128                            href="https://qudsn.co/category_news/%D8%A3%D8%AE%D8%A8%D8%A7%D8%B1">أخبار</a>
1129                    </li>
1130                                    <li><a
1131                            href="https://qudsn.co/category_news/%D9%85%D8%AF%D9%88%D9%91%D9%86%D8%A7%D8%AA">مدوّنات</a>
1132                    </li>
1133                                    <li><a
1134                            href="https://qudsn.co/category_news/%D8%AA%D8%B1%D8%AC%D9%85%D8%A7%D8%AA%20%D8%B9%D8%A8%D8%B1%D9%8A%D8%A9">ترجمات عبرية</a>
1135                    </li>
1136                                    <li><a
1137                            href="https://qudsn.co/category_news/%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA">مقالات</a>
1138                    </li>
1139                                    <li><a
1140                            href="https://qudsn.co/category_news/%D8%AA%D9%82%D8%A7%D8%B1%D9%8A%D8%B1%20%D9%82%D8%AF%D8%B3">تقارير قدس</a>
1141                    </li>
1142                                    <li><a
1143                            href="https://qudsn.co/category_news/%D9%85%D8%AA%D8%A7%D8%A8%D8%B9%D8%A7%D8%AA%20%D9%82%D8%AF%D8%B3">متابعات قدس</a>
1144                    </li>
1145                                <li><a href="https://qudsn.co/all_podcast">بودكاست قدس</a></li>
1146            </ul>
1147        </nav>
1148
1149        <nav class="footer__col" aria-label="روابط">
1150            <h3 class="footer__title">روابط</h3>
1151            <ul class="footer__links">
1152                                    <li><a href="https://qudsn.co/about_us">من نحن</a></li>
1153                    <li><a href="https://qudsn.co/contact_us">تواصل معنا</a></li>
1154                    <li><a href="https://qudsn.co/all_posts">كل المقالات</a></li>
1155                    <li><a href="https://qudsn.co/all_videos">قدس فيديو</a></li>
1156                    <li><a href="https://qudsnen.com" target="_blank" rel="noopener">English</a></li>
1157                            </ul>
1158        </nav>
1159
1160        
1161                    <div class="footer__col">
1162                <h3 class="footer__title">تابعونا مباشرة</h3>
1163                                    
1164                    <a href="https://t.me/QudsN" class="footer-channel"
1165                        style="--ch:#26a5e4" target="_blank" rel="noopener">
1166                                                    <i class="fab fa-telegram" aria-hidden="true"></i>
1167                                                <span><span class="footer-channel__name">قناة تيليغرام</span>
1168                                                            <span class="footer-channel__sub">آخر الأخبار العاجلة</span>
1169                                                    </span>
1170                    </a>
1171                                    
1172                    <a href="https://whatsapp.com/channel/0029VbCpgXo8fewqkgDTLM2z" class="footer-channel"
1173                        style="--ch:#25d366" target="_blank" rel="noopener">
1174                                                    <i class="fab fa-whatsapp" aria-hidden="true"></i>
1175                                                <span><span class="footer-channel__name">قناة واتساب</span>
1176                                                            <span class="footer-channel__sub">انضم الآن</span>
1177                                                    </span>
1178                    </a>
1179                            </div>
1180            </div>
1181
1182    <div class="container footer__bottom">
1183        <p>شبكة قدس الإخبارية جميع حقوق النشر محفوظة © 2026</p>
1184        <p>تصميم وتطوير بعناية لتجربة قراءة أفضل</p>
1185    </div>
1186</footer>
1187
1188    
1189    <div class="breaking-float" id="qdBreakingFloat" hidden
1190        data-endpoint="/breaking_latest"
1191                data-interval="90" role="status" aria-live="polite">
1192        <div class="breaking-float__inner">
1193            <span class="breaking-float__label"><span class="breaking-float__pulse" aria-hidden="true"></span>عاجل</span>
1194            <a class="breaking-float__title" href="#"></a>
1195            <button type="button" class="breaking-float__close" aria-label="إغلاق شريط العاجل">
1196                <i class="fa-solid fa-xmark" aria-hidden="true"></i>
1197            </button>
1198        </div>
1199    </div>
1200
1201
1202<div class="search-overlay" id="searchOverlay" role="dialog" aria-modal="true" aria-label="البحث في شبكة قدس" hidden>
1203    <div class="search-box" role="document">
1204        <div class="search-box__head">
1205            <h3 class="search-box__title">ابحث في شبكة قدس الإخبارية</h3>
1206            <button type="button" class="icon-btn" id="searchClose" aria-label="إغلاق البحث"><i
1207                    class="fa-solid fa-xmark" aria-hidden="true"></i></button>
1208        </div>
1209        <form class="search-box__field" role="search" action="https://qudsn.co/search_page" method="get">
1210            <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
1211            <label class="sr-only" for="searchInput">كلمة البحث</label>
1212            <input id="searchInput" name="search" type="search" autocomplete="off"
1213                role="combobox" aria-expanded="false" aria-controls="searchSuggest"
1214                placeholder="ابحث في الأخبار والتقارير والمقالات...">
1215        </form>
1216
1217        
1218        <div class="search-suggest" id="searchSuggest" role="listbox" aria-label="اقتراحات البحث" hidden></div>
1219
1220        
1221        <div class="search-box__trending" id="searchTrending">
1222            <span class="search-box__trending-label">الأكثر بحثًا</span>
1223            <div class="search-box__tags">
1224                                    <a href="https://qudsn.co/search_page?search=%D8%BA%D8%B2%D8%A9" class="tag">غزة</a>
1225                                    <a href="https://qudsn.co/search_page?search=%D8%A7%D9%84%D8%B6%D9%81%D8%A9+%D8%A7%D9%84%D8%BA%D8%B1%D8%A8%D9%8A%D8%A9" class="tag">الضفة الغربية</a>
1226                                    <a href="https://qudsn.co/search_page?search=%D8%A7%D9%84%D9%82%D8%AF%D8%B3" class="tag">القدس</a>
1227                                    <a href="https://qudsn.co/search_page?search=%D8%A7%D9%84%D8%A3%D8%B3%D8%B1%D9%89" class="tag">الأسرى</a>
1228                                    <a href="https://qudsn.co/search_page?search=%D9%88%D9%82%D9%81+%D8%A5%D8%B7%D9%84%D8%A7%D9%82+%D8%A7%D9%84%D9%86%D8%A7%D8%B1" class="tag">وقف إطلاق النار</a>
1229                                    <a href="https://qudsn.co/search_page?search=%D8%AA%D8%B1%D8%AC%D9%85%D8%A7%D8%AA+%D8%B9%D8%A8%D8%B1%D9%8A%D8%A9" class="tag">ترجمات عبرية</a>
1230                            </div>
1231        </div>
1232    </div>
1233</div>
vendor: 111 bytes, lines 1233-1236
1233
1234
1235
1236<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
1236<script src="/assets/main/quds_ar/js/main.js?v=1789735121" data-swiper-src="/assets/main/quds_ar/vendor/swiper/swiper-bundle.min.js?v=1789735121" defer></script>
1236
1237
1238
1239<script>
1240(function () {
1241    var input = document.getElementById('searchInput');
1242    var box = document.getElementById('searchSuggest');
1243    var trending = document.getElementById('searchTrending');
1244    if (!input || !box) return;
1245
1246    
1247    var endpoint = "\/search_suggest";
1248    var timer = null, controller = null, activeIdx = -1;
1249
1250    function esc(s) {
1251        return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
1252            return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
1253        });
1254    }
1255    function expanded(v) { input.setAttribute('aria-expanded', v ? 'true' : 'false'); }
1256    function showTrending() {
1257        box.hidden = true; box.innerHTML = ''; activeIdx = -1; expanded(false);
1258        if (trending) trending.hidden = false;
1259    }
1260    function state(html) {
1261        if (trending) trending.hidden = true;
1262        box.hidden = false; expanded(true);
1263        box.innerHTML = '<div class="search-suggest__state">' + html + '</div>';
1264    }
1265    function setActive(i) {
1266        var nodes = box.querySelectorAll('.search-suggest__item');
1267        nodes.forEach(function (n) { n.classList.remove('is-active'); });
1268        if (i >= 0 && i < nodes.length) { activeIdx = i; nodes[i].classList.add('is-active'); nodes[i].scrollIntoView({ block: 'nearest' }); }
1269        else { activeIdx = -1; }
1270    }
1271    function render(items) {
1272        if (trending) trending.hidden = true;
1273        box.hidden = false; expanded(true); activeIdx = -1;
1274        if (!items || !items.length) {
1275            box.innerHTML = '<div class="search-suggest__state">لا توجد نتائج مطابقة — اضغط Enter للبحث الكامل</div>';
1276            return;
1277        }
1278        var ICONS = { article: 'fa-newspaper', podcast: 'fa-microphone', video: 'fa-play', tag: 'fa-hashtag' };
1279        box.innerHTML = items.map(function (it) {
1280            var ic = ICONS[it.type] || 'fa-newspaper';
1281            return '<a class="search-suggest__item search-suggest__item--' + (it.type || 'article') + '" role="option" href="' + esc(it.url) + '">' +
1282                '<span class="search-suggest__cat"><i class="fa-solid ' + ic + '" aria-hidden="true"></i>' + esc(it.category) + '</span>' +
1283                '<span class="search-suggest__title">' + esc(it.title) + '</span></a>';
1284        }).join('');
1285    }
1286    function fetchSuggest(q) {
1287        if (controller) controller.abort();
1288        controller = new AbortController();
1289        state('<i class="fa-solid fa-spinner fa-spin" aria-hidden="true"></i> جارٍ البحث…');
1290        fetch(endpoint + '?q=' + encodeURIComponent(q), { signal: controller.signal, headers: { 'X-Requested-With': 'XMLHttpRequest' } })
1291            .then(function (r) { return r.json(); })
1292            .then(function (d) { render(d.items || []); })
1293            .catch(function (e) { if (e.name !== 'AbortError') showTrending(); });
1294    }
1295
1296    input.addEventListener('input', function () {
1297        var q = input.value.trim();
1298        clearTimeout(timer);
1299        if (q.length < 2) { showTrending(); return; }
1300        timer = setTimeout(function () { fetchSuggest(q); }, 220);
1301    });
1302    input.addEventListener('keydown', function (e) {
1303        var nodes = box.querySelectorAll('.search-suggest__item');
1304        if (box.hidden || !nodes.length) return;
1305        if (e.key === 'ArrowDown') { e.preventDefault(); setActive(Math.min(activeIdx + 1, nodes.length - 1)); }
1306        else if (e.key === 'ArrowUp') { e.preventDefault(); setActive(Math.max(activeIdx - 1, 0)); }
1307        else if (e.key === 'Enter' && activeIdx >= 0) { e.preventDefault(); window.location.href = nodes[activeIdx].getAttribute('href'); }
1308    });
1309})();
1310</script>
1310
1311
1312
1313<script>
1314(function () {
1315    var forms = document.querySelectorAll('.js-news-form');
1316    if (!forms.length) return;
1317    // Path only, not route()'s absolute URL. That URL is built from APP_URL, so
1318    // an http:// value behind an https:// site makes the browser block the
1319    // request as mixed content and the form reports «تعذّر الاتصال بالخادم»
1320    // even though the endpoint is fine. A relative path always matches the
1321    // scheme and host the visitor is actually on.
1322    var endpoint = "\/newsletter_subscribe";
1323
1324    forms.forEach(function (form) {
1325        form.addEventListener('submit', function (e) {
1326            e.preventDefault();
1327            var wrap = form.closest('.newsletter__form-wrap') || form.parentNode;
1328            var input = form.querySelector('input[type="email"], input[name="email"]');
1329            var success = wrap.querySelector('.news-form__success');
1330            var error = wrap.querySelector('.news-form__error');
1331            var btn = form.querySelector('button[type="submit"]');
1332            if (error) { error.hidden = true; error.textContent = ''; }
1333            if (input && !input.checkValidity()) { input.reportValidity(); return; }
1334
1335            var email = input ? input.value.trim() : '';
1336            if (btn) btn.disabled = true;
1337
1338            fetch(endpoint, {
1339                method: 'POST',
1340                headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
1341                body: JSON.stringify({ email: email })
1342            })
1343            .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, body: d }; }); })
1344            .then(function (res) {
1345                if (res.ok && res.body && res.body.ok) {
1346                    form.hidden = true;
1347                    if (success) success.hidden = false;
1348                } else {
1349                    if (error) { error.textContent = (res.body && res.body.message) || 'تعذّر الاشتراك، حاول مرة أخرى.'; error.hidden = false; }
1350                    if (btn) btn.disabled = false;
1351                }
1352            })
1353            .catch(function () {
1354                if (error) { error.textContent = 'تعذّر الاتصال بالخادم، حاول مرة أخرى.'; error.hidden = false; }
1355                if (btn) btn.disabled = false;
1356            });
1357        });
1358    });
1359})();
1360</script>
1360
1361
1362
1363
1364    <style>
1365    /* ---------- Carousel ---------- */
1366    .post-gallery { margin: 1.5em auto; max-width: 100%; }
1367    .pg-carousel { position: relative; border-radius: 12px; overflow: hidden; background: #0b0f1a; }
1368    .pg-viewport { overflow: hidden; }
1369    .pg-track { display: flex; transition: transform .4s cubic-bezier(.22,.61,.36,1); will-change: transform; }
1370    .pg-slide { flex: 0 0 100%; min-width: 100%; }
1371    .pg-slide img {
1372        display: block; width: 100%; height: 100%;
1373        aspect-ratio: 16 / 9; object-fit: cover; cursor: zoom-in; margin: 0;
1374    }
1375    /* Grid layout — media side by side instead of one at a time. */
1376    .pg-grid {
1377        display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
1378        gap: 10px; margin: 28px auto; max-width: 100%;
1379    }
1380    /* Wider grid, opt-in via data-pg-layout="grid-3". */
1381    .pg-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1382    /* Mosaic: the lead photo carries the block, the rest support it — the
1383       shape newsrooms actually use for a photo set. */
1384    .pg-mosaic { grid-template-columns: repeat(4, minmax(0, 1fr)); }
1385    .pg-mosaic .pg-cell:first-child { grid-column: span 2; grid-row: span 2; }
1386    .pg-grid .pg-cell { position: relative; min-width: 0; border-radius: 10px; overflow: hidden; background: #0b1220; }
1387    .pg-grid .pg-cell img,
1388    .pg-grid .pg-cell video {
1389        display: block; width: 100%; height: 100%;
1390        aspect-ratio: 16 / 9; object-fit: cover; margin: 0;
1391    }
1392    .pg-grid .pg-cell video { object-fit: contain; background: #000; }
1393    .pg-grid .pg-cell img { cursor: zoom-in; }
1394    .pg-grid .pg-cell--doc { display: grid; place-items: center; aspect-ratio: 16 / 9; padding: 14px; }
1395    /* A lone item should not sit in a half-width column. */
1396    .pg-grid .pg-cell:only-child { grid-column: 1 / -1; }
1397    @media (max-width: 640px) {
1398        .pg-grid, .pg-grid--3 { grid-template-columns: 1fr; }
1399        /* Mosaic keeps two columns on a phone — one column would erase it. */
1400        .pg-mosaic { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1401    }
1402
1403    /* ---------- Aspect ratio (data-pg-ratio) ----------
1404       Absent = 16/9 cover, exactly as every gallery authored before this. */
1405    [data-pg-ratio="4-3"] .pg-slide img, [data-pg-ratio="4-3"] .pg-cell img,
1406    [data-pg-ratio="4-3"] .pg-slide video, [data-pg-ratio="4-3"] .pg-cell video { aspect-ratio: 4 / 3; }
1407    [data-pg-ratio="1-1"] .pg-slide img, [data-pg-ratio="1-1"] .pg-cell img,
1408    [data-pg-ratio="1-1"] .pg-slide video, [data-pg-ratio="1-1"] .pg-cell video { aspect-ratio: 1 / 1; }
1409    /* "auto" = never crop. Portrait photography survives; the cap keeps a tall
1410       shot from pushing the article off the screen. */
1411    [data-pg-ratio="auto"] .pg-slide img, [data-pg-ratio="auto"] .pg-cell img {
1412        aspect-ratio: auto; height: auto; max-height: 72vh; object-fit: contain; background: #0b0f1a;
1413    }
1414    [data-pg-ratio="auto"].pg-grid .pg-cell { background: transparent; }
1415
1416    /* ---------- Captions ----------
1417       Carousel: ONE strip under the viewport that follows the active slide, so
1418       slide heights stay uniform (a per-slide caption made the track jump).
1419       Grid: a caption under each cell. */
1420    .pg-cap {
1421        font-size: 13px; line-height: 1.65; color: #475569;
1422        padding: 8px 12px 0; text-align: center;
1423    }
1424    .pg-cap:empty { display: none; }
1425    .pg-grid .pg-cap { text-align: start; padding: 6px 2px 0; font-size: 12.5px; }
1426    /* The cell is the caption's parent, so it must not clip it. */
1427    .pg-grid .pg-cell--captioned { overflow: visible; background: transparent; }
1428    .pg-grid .pg-cell--captioned > img,
1429    .pg-grid .pg-cell--captioned > video { border-radius: 10px; }
1430
1431    /* ---------- "+N more" overflow badge ---------- */
1432    .pg-more {
1433        position: absolute; inset: 0; display: grid; place-items: center;
1434        background: rgba(6, 9, 18, .62); color: #fff;
1435        font-size: 22px; font-weight: 800; letter-spacing: .02em;
1436        cursor: zoom-in; direction: ltr; border: 0; width: 100%; padding: 0;
1437    }
1438    .pg-more:hover { background: rgba(6, 9, 18, .74); }
1439
1440    /* Video slide: same 16/9 frame as an image slide, native controls. */
1441    .pg-slide video {
1442        display: block; width: 100%; height: 100%;
1443        aspect-ratio: 16 / 9; object-fit: contain; background: #000; margin: 0;
1444    }
1445    /* Audio slide: a centred player with the track name above it. */
1446    .pg-slide--audio, .pg-grid .pg-cell--audio {
1447        display: grid; place-items: center; align-content: center; gap: 10px;
1448        aspect-ratio: 16 / 9; padding: 18px;
1449    }
1450    .pg-slide--audio audio, .pg-grid .pg-cell--audio audio {
1451        width: min(100%, 420px); height: 40px; display: block; margin: 0;
1452    }
1453    /* Track name comes from data-pg-name; ::before keeps the markup a lone <audio>. */
1454    .pg-slide--audio::before, .pg-grid .pg-cell--audio::before {
1455        content: attr(data-pg-label);
1456        font-size: 13px; font-weight: 700; color: #e2e8f0; text-align: center;
1457        max-width: 90%; line-height: 1.6; word-break: break-word;
1458    }
1459    .pg-slide--audio:not([data-pg-label])::before,
1460    .pg-grid .pg-cell--audio:not([data-pg-label])::before { content: none; }
1461
1462    /* Document slide (PDF / Word / anything else): a card, not a preview. */
1463    .pg-slide--doc { display: grid; place-items: center; aspect-ratio: 16 / 9; padding: 18px; }
1464    .pg-doc {
1465        display: flex; flex-direction: column; align-items: center; gap: 6px;
1466        max-width: 90%; padding: 18px 22px; border-radius: 12px; text-decoration: none;
1467        background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16);
1468        color: #f1f5f9; text-align: center; transition: background .15s ease, transform .15s ease;
1469    }
1470    .pg-doc:hover { background: rgba(255,255,255,.12); transform: translateY(-1px); }
1471    /* The glyph is drawn in CSS so no icon font is required by any theme. */
1472    .pg-doc::before {
1473        content: ''; width: 34px; height: 42px; margin-bottom: 4px;
1474        background: #e2e8f0; border-radius: 3px;
1475        clip-path: polygon(0 0, 68% 0, 100% 26%, 100% 100%, 0 100%);
1476    }
1477    .pg-doc__name { font-size: 14px; font-weight: 700; word-break: break-word; line-height: 1.5; }
1478    .pg-doc__meta { font-size: 11.5px; opacity: .75; direction: ltr; }
1479
1480    .pg-arrow {
1481        position: absolute; top: 50%; transform: translateY(-50%);
1482        width: 42px; height: 42px; border: none; border-radius: 50%;
1483        background: rgba(0,0,0,.5); color: #fff; font-size: 22px; line-height: 1;
1484        cursor: pointer; display: grid; place-items: center; z-index: 2; padding: 0;
1485        transition: background .15s ease, opacity .15s ease;
1486    }
1487    .pg-arrow:hover { background: rgba(0,0,0,.78); }
1488    .pg-prev { inset-inline-start: 10px; }
1489    .pg-next { inset-inline-end: 10px; }
1490    .pg-counter {
1491        position: absolute; top: 10px; inset-inline-end: 12px; z-index: 2;
1492        background: rgba(0,0,0,.55); color: #fff; font-size: 12px; font-weight: 600;
1493        padding: 3px 9px; border-radius: 999px; direction: ltr;
1494    }
1495    .pg-dots { display: flex; gap: 2px; justify-content: center; padding: 10px; flex-wrap: wrap; }
1496    /* a11y target-size: same 8px visual dot, but a 24x24 hit area (the dot is
1497       drawn as a centered radial gradient inside the bigger button) */
1498    .pg-dot {
1499        width: 24px; height: 24px; border-radius: 50%; border: none; padding: 0; cursor: pointer;
1500        background: radial-gradient(circle 4px at center, rgba(255,255,255,.35) 100%, transparent 0);
1501        transition: background .15s ease, transform .15s ease;
1502    }
1503    .pg-dot.is-active { background: radial-gradient(circle 5px at center, #fff 100%, transparent 0); }
1504    .pg-carousel--single .pg-arrow, .pg-carousel--single .pg-dots, .pg-carousel--single .pg-counter { display: none; }
1505
1506    /* ---------- Lightbox ---------- */
1507    .pg-lightbox {
1508        position: fixed; inset: 0; z-index: 2147483000;
1509        background: rgba(6,8,15,.94); display: none;
1510        align-items: center; justify-content: center;
1511        opacity: 0; transition: opacity .2s ease;
1512        touch-action: none;
1513    }
1514    .pg-lightbox.is-open { display: flex; opacity: 1; }
1515    .pg-lb-stage { position: relative; width: 100%; height: 100%; display: grid; place-items: center; padding: 56px 16px; }
1516    .pg-lb-img { max-width: 94vw; max-height: 86vh; object-fit: contain; border-radius: 6px; box-shadow: 0 10px 40px rgba(0,0,0,.5); user-select: none; }
1517    .pg-lb-btn {
1518        position: absolute; border: none; background: rgba(255,255,255,.12); color: #fff;
1519        cursor: pointer; display: grid; place-items: center; border-radius: 50%;
1520        padding: 0; line-height: 1; transition: background .15s ease;
1521        /* The ❮ / ❯ ornaments are Bidi_Mirrored=Yes — inside an RTL page they get
1522           flipped by the text engine. Force LTR so the glyph renders as authored.
1523           (Buttons are positioned with physical left/right, so this is safe.) */
1524        direction: ltr;
1525    }
1526    .pg-lb-btn:hover { background: rgba(255,255,255,.26); }
1527    .pg-lb-close { top: 16px; left: 50%; transform: translateX(-50%); width: 44px; height: 44px; font-size: 26px; }
1528    .pg-lb-nav { top: 50%; transform: translateY(-50%); width: 52px; height: 52px; font-size: 30px; }
1529    /* Physical left/right (NOT logical) so the arrows always sit & point outward,
1530       immune to however a theme sets direction on the page or its buttons. */
1531    .pg-lb-prev { left: 16px; }
1532    .pg-lb-next { right: 16px; }
1533    .pg-lb-counter { position: absolute; top: 24px; inset-inline-start: 18px; color: #e5e7eb; font-size: 13px; font-weight: 600; direction: ltr; }
1534    /* Zoom: the image scales inside the stage, which then pans by drag. */
1535    .pg-lb-img { transition: transform .18s ease; transform-origin: center center; }
1536    .pg-lb-img.is-zoomed { cursor: grab; max-width: none; max-height: none; }
1537    .pg-lb-tools {
1538        position: absolute; top: 16px; inset-inline-end: 18px;
1539        display: flex; align-items: center; gap: 6px; direction: ltr;
1540    }
1541    .pg-lb-tool {
1542        width: 34px; height: 34px; border: 0; border-radius: 8px; cursor: pointer;
1543        background: rgba(255,255,255,.12); color: #fff; font-size: 16px; line-height: 1;
1544        display: grid; place-items: center; text-decoration: none; padding: 0;
1545        transition: background .15s ease;
1546    }
1547    .pg-lb-tool:hover { background: rgba(255,255,255,.26); }
1548    .pg-lb-zoomlabel { color: #cbd5e1; font-size: 12px; font-weight: 700; min-width: 40px; text-align: center; }
1549    .pg-lb-caption {
1550        position: absolute; bottom: 14px; inset-inline: 0; text-align: center;
1551        color: #e5e7eb; font-size: 14px; padding: 0 60px; line-height: 1.5;
1552        text-shadow: 0 1px 3px rgba(0,0,0,.8);
1553    }
1554    @media (max-width: 576px) {
1555        .pg-slide img { aspect-ratio: 4 / 3; }
1556        .pg-lb-nav { width: 42px; height: 42px; font-size: 24px; }
1557        .pg-lb-caption { font-size: 12.5px; padding: 0 16px; bottom: 8px; }
1558    }
1559</style>
1560<script>
1561(function () {
1562    if (window.__postGalleryReady) return;
1563    window.__postGalleryReady = true;
1564
1565    // Control labels come from the SITE's lang files. They used to be baked
1566    // into this script — an Arabic dot label ("الانتقال إلى الصورة") shipped
1567    // to English sites, and English Previous/Next/Close shipped to Arabic
1568    // ones. Defaults keep the partial safe if a key is ever missing.
1569    var T = Object.assign({
1570        prev: 'Previous', next: 'Next', close: 'Close',
1571        goTo: 'Go to image', zoomIn: 'Zoom in', zoomOut: 'Zoom out',
1572        openOriginal: 'Open the original',
1573    }, {"prev":"\u0627\u0644\u0633\u0627\u0628\u0642","next":"\u0627\u0644\u062a\u0627\u0644\u064a","close":"\u0625\u063a\u0644\u0627\u0642","goTo":"\u0627\u0644\u0627\u0646\u062a\u0642\u0627\u0644 \u0625\u0644\u0649 \u0627\u0644\u0635\u0648\u0631\u0629","zoomIn":"\u062a\u0643\u0628\u064a\u0631","zoomOut":"\u062a\u0635\u063a\u064a\u0631","openOriginal":"\u0641\u062a\u062d \u0627\u0644\u0635\u0648\u0631\u0629 \u0627\u0644\u0623\u0635\u0644\u064a\u0629"} || {});
1574
1575    // Direction is read from each element's OWN computed `direction` — never from
1576    // <html>. Some themes keep dir="rtl" on <html> but render the body/overlay LTR
1577    // via CSS; reading the element keeps the arrow GLYPH, its logical POSITION and
1578    // the swipe/key LOGIC all derived from one source, so they can never disagree.
1579    function elRTL(el) { try { return getComputedStyle(el).direction === 'rtl'; } catch (e) { return false; } }
1580    // prev sits on the inline-start edge, next on inline-end, so the glyph that
1581    // "points outward" flips with direction. LTR: prev ❮ / next ❯. RTL: mirrored.
1582    function prevGlyph(rtl) { return rtl ? '&#10095;' : '&#10094;'; }
1583    function nextGlyph(rtl) { return rtl ? '&#10094;' : '&#10095;'; }
1584    // Images eligible for the lightbox: gallery slides, editor figures, and the
1585    // usual content wrappers. Header/footer/logo images are excluded by scope.
1586    var IMG_SELECTOR = [
1587        '[data-post-gallery] img',
1588        'figure.post-image-figure img',
1589        '.post-content img', '.article-content img', '.entry-content img',
1590        '.single-post img', '[data-post-body] img'
1591    ].join(',');
1592
1593    // ...but those wrappers are PAGE-level: `.article-content` on several
1594    // themes wraps the header too, so the byline avatar and the share
1595    // control's icon sat inside it and the lightbox happily opened them as
1596    // photographs. An <img> that lives inside an interactive control or a
1597    // byline is chrome, never a picture the reader asked to enlarge.
1598    var CHROME_SELECTOR = [
1599        'button', '[role="button"]',
1600        '.share-wrap', '.share-menu', '.social-share__link',
1601        '.post-meta', '.article-meta', '.author-face',
1602        '[data-pg-skip]'
1603    ].join(',');
1604
1605    /**
1606     * Is this <img> something the lightbox should open?
1607     * Explicit gallery markup always wins; otherwise the image must sit in a
1608     * content wrapper, outside chrome, and be big enough to be a photograph.
1609     */
1610    function isLightboxImage(img) {
1611        if (!img || !img.matches || !img.matches(IMG_SELECTOR)) return false;
1612        if (img.closest('[data-post-gallery]')) return true;
1613        if (img.closest(CHROME_SELECTOR)) return false;
1614        // An icon is not a photograph. Measured, not guessed — and only when
1615        // the image actually has a box, so a not-yet-laid-out image is kept.
1616        var r = img.getBoundingClientRect();
1617        if (r.width && r.height && r.width < 40 && r.height < 40) return false;
1618        return true;
1619    }
1620
1621    /* ----------------------------- Carousel ----------------------------- */
1622    // A slide can be an image, a video, or a document card. Images keep being
1623    // plain <img> children (so galleries authored before this stayed valid and
1624    // the lightbox still finds them); the other kinds are opt-in markup emitted
1625    // by the editor. Direct children only — never reaches into a card's insides.
1626    var MEDIA_SELECTOR = ':scope > img, :scope > video, :scope > audio, :scope > [data-pg-doc]';
1627    function galleryMedia(gal) {
1628        try {
1629            return Array.prototype.slice.call(gal.querySelectorAll(MEDIA_SELECTOR));
1630        } catch (e) {
1631            // :scope is unsupported on very old engines — fall back to filtering.
1632            return Array.prototype.filter.call(gal.children, function (el) {
1633                return el.matches && el.matches('img, video, audio, [data-pg-doc]');
1634            });
1635        }
1636    }
1637
1638    /** Slide modifier for a media node — images get none (the default look). */
1639    function slideKindClass(node) {
1640        if (node.tagName === 'VIDEO') return 'pg-slide--video';
1641        if (node.tagName === 'AUDIO') return 'pg-slide--audio';
1642        if (node.hasAttribute && node.hasAttribute('data-pg-doc')) return 'pg-slide--doc';
1643        return '';
1644    }
1645
1646    /** Audio slides label themselves from the node's data-pg-name (CSS ::before). */
1647    function applySlideLabel(wrapper, node) {
1648        if (node.tagName !== 'AUDIO') return;
1649        var name = node.getAttribute('data-pg-name');
1650        if (name) wrapper.setAttribute('data-pg-label', name);
1651    }
1652
1653    /** A media node's caption, authored on the node itself as data-pg-caption. */
1654    function mediaCaption(node) {
1655        return (node.getAttribute && node.getAttribute('data-pg-caption') || '').trim();
1656    }
1657
1658    /**
1659     * Below-the-fold gallery images should not block the article's own render.
1660     * The editor now stamps loading/decoding at insert time, but EVERY gallery
1661     * authored before that is still out there — mark them here. The first item
1662     * stays eager: it is often the one in view.
1663     */
1664    function applyLoadingHints(media) {
1665        media.forEach(function (node, i) {
1666            if (node.tagName !== 'IMG' || node.hasAttribute('loading')) return;
1667            if (i > 0) node.setAttribute('loading', 'lazy');
1668            node.setAttribute('decoding', 'async');
1669        });
1670    }
1671
1672    function enhanceGalleries(root) {
1673        var scope = root || document;
1674        var galleries = scope.querySelectorAll('[data-post-gallery]:not([data-pg-ready])');
1675        galleries.forEach(function (gal) {
1676            var media = galleryMedia(gal);
1677            if (!media.length) { gal.setAttribute('data-pg-ready', '1'); return; }
1678
1679            applyLoadingHints(media);
1680
1681            // Grid family: everything side by side, no arrows/dots/counter.
1682            // Opt-in via data-pg-layout — a block WITHOUT the attribute stays a
1683            // carousel, which is every gallery authored before this existed,
1684            // and "grid" keeps meaning exactly the two-column layout it always
1685            // did. New values are additive only.
1686            var layout = gal.getAttribute('data-pg-layout') || '';
1687            var GRIDS = { 'grid': '', 'grid-3': 'pg-grid--3', 'mosaic': 'pg-mosaic' };
1688            if (layout in GRIDS) {
1689                gal.classList.add('pg-grid');
1690                if (GRIDS[layout]) gal.classList.add(GRIDS[layout]);
1691                gal.setAttribute('data-pg-ready', '1');
1692
1693                // data-pg-max caps how many cells are drawn; the last visible one
1694                // wears a "+N" badge that opens the lightbox at the first hidden
1695                // item, so nothing becomes unreachable.
1696                var max = parseInt(gal.getAttribute('data-pg-max') || '0', 10);
1697                var shown = (max > 0 && max < media.length) ? max : media.length;
1698                var hidden = media.length - shown;
1699
1700                var cells = document.createDocumentFragment();
1701                media.forEach(function (node, i) {
1702                    var cap = mediaCaption(node);
1703                    if (i >= shown) {
1704                        // Kept in the DOM (hidden) so the lightbox list still
1705                        // contains it — only its cell is not drawn.
1706                        node.style.display = 'none';
1707                        cells.appendChild(node);
1708                        return;
1709                    }
1710                    var cell = document.createElement('div'); cell.className = 'pg-cell';
1711                    var cellCls = slideKindClass(node).replace('pg-slide--', 'pg-cell--');
1712                    if (cellCls) cell.classList.add(cellCls);
1713                    if (cap) cell.classList.add('pg-cell--captioned');
1714                    applySlideLabel(cell, node);
1715                    cell.appendChild(node);
1716                    if (cap) {
1717                        var c = document.createElement('div'); c.className = 'pg-cap'; c.textContent = cap;
1718                        cell.appendChild(c);
1719                    }
1720                    if (hidden > 0 && i === shown - 1) {
1721                        var more = document.createElement('button');
1722                        more.type = 'button'; more.className = 'pg-more';
1723                        more.setAttribute('data-pg-more', String(shown));
1724                        more.textContent = '+' + hidden;
1725                        cell.appendChild(more);
1726                    }
1727                    cells.appendChild(cell);
1728                });
1729                gal.innerHTML = '';
1730                gal.appendChild(cells);
1731                // Images keep their [data-post-gallery] img ancestry, so the
1732                // lightbox picks them up here exactly as in a carousel.
1733                return;
1734            }
1735
1736            gal.classList.add('pg-carousel');
1737            gal.setAttribute('data-pg-ready', '1');
1738            if (media.length === 1) gal.classList.add('pg-carousel--single');
1739
1740            var viewport = document.createElement('div'); viewport.className = 'pg-viewport';
1741            var track = document.createElement('div'); track.className = 'pg-track';
1742            media.forEach(function (node) {
1743                var slide = document.createElement('div'); slide.className = 'pg-slide';
1744                var kindCls = slideKindClass(node);
1745                if (kindCls) slide.classList.add(kindCls);
1746                applySlideLabel(slide, node);
1747                slide.appendChild(node);
1748                track.appendChild(slide);
1749            });
1750            viewport.appendChild(track);
1751            gal.innerHTML = '';
1752            gal.appendChild(viewport);
1753
1754            // ONE caption strip under the viewport, refreshed per slide — a
1755            // per-slide caption made every slide a different height and the
1756            // track jumped on each move.
1757            if (media.some(function (n) { return mediaCaption(n); })) {
1758                var capEl = document.createElement('div');
1759                capEl.className = 'pg-cap';
1760                gal.appendChild(capEl);
1761            }
1762
1763            gal.setAttribute('data-pg-index', '0');
1764            var galRTL = elRTL(gal);
1765            var counter = document.createElement('div'); counter.className = 'pg-counter';
1766            // <bdo dir="ltr"> stops the Bidi_Mirrored ❮/❯ from being flipped by an
1767            // RTL page, while the button keeps its logical (direction-aware) position.
1768            var prev = document.createElement('button'); prev.type = 'button'; prev.className = 'pg-arrow pg-prev'; prev.setAttribute('data-pg-go', 'prev'); prev.setAttribute('aria-label', T.prev); prev.innerHTML = '<bdo dir="ltr">' + prevGlyph(galRTL) + '</bdo>';
1769            var next = document.createElement('button'); next.type = 'button'; next.className = 'pg-arrow pg-next'; next.setAttribute('data-pg-go', 'next'); next.setAttribute('aria-label', T.next); next.innerHTML = '<bdo dir="ltr">' + nextGlyph(galRTL) + '</bdo>';
1770            var dots = document.createElement('div'); dots.className = 'pg-dots';
1771            media.forEach(function (_, i) {
1772                var d = document.createElement('button'); d.type = 'button'; d.className = 'pg-dot' + (i === 0 ? ' is-active' : ''); d.setAttribute('data-pg-dot', i);
1773                d.setAttribute('aria-label', T.goTo + ' ' + (i + 1));
1774                dots.appendChild(d);
1775            });
1776            gal.appendChild(counter); gal.appendChild(prev); gal.appendChild(next); gal.appendChild(dots);
1777            applyCarousel(gal);
1778        });
1779    }
1780
1781    // Controls are handled by the ONE delegated listener below — not per-button
1782    // listeners — so navigation keeps working even when Livewire/theme scripts
1783    // re-render the post body and drop per-node listeners (that's exactly why the
1784    // lightbox survived but the arrows didn't). State lives on the DOM element.
1785    function applyCarousel(gal) {
1786        var track = gal.querySelector('.pg-track');
1787        if (!track) return;
1788        var count = track.children.length || 1;
1789        var index = parseInt(gal.getAttribute('data-pg-index') || '0', 10);
1790        index = ((index % count) + count) % count;
1791        gal.setAttribute('data-pg-index', index);
1792        track.style.transform = 'translateX(' + ((elRTL(gal) ? 1 : -1) * index * 100) + '%)';
1793        var counter = gal.querySelector('.pg-counter'); if (counter) counter.textContent = (index + 1) + ' / ' + count;
1794        gal.querySelectorAll('.pg-dot').forEach(function (d, i) { d.classList.toggle('is-active', i === index); });
1795        var capEl = gal.querySelector(':scope > .pg-cap');
1796        if (capEl) {
1797            var active = track.children[index];
1798            var node = active && active.firstElementChild;
1799            capEl.textContent = node ? mediaCaption(node) : '';
1800        }
1801        pauseHiddenVideos(gal);
1802    }
1803    function moveCarousel(gal, delta) {
1804        gal.setAttribute('data-pg-index', parseInt(gal.getAttribute('data-pg-index') || '0', 10) + delta);
1805        applyCarousel(gal);
1806    }
1807
1808    // Leaving a slide stops its video or audio, so navigating never leaves sound
1809    // playing behind the reader. No-op on galleries that hold only images.
1810    function pauseHiddenVideos(gal) {
1811        var index = parseInt(gal.getAttribute('data-pg-index') || '0', 10);
1812        gal.querySelectorAll('.pg-slide').forEach(function (slide, i) {
1813            if (i === index) return;
1814            slide.querySelectorAll('video, audio').forEach(function (v) {
1815                try { if (!v.paused) v.pause(); } catch (e) {}
1816            });
1817        });
1818    }
1819
1820    /* ----------------------------- Lightbox ----------------------------- */
1821    var lb, lbImg, lbCaption, lbCounter, lbList = [], lbIndex = 0, lbTouchX = 0;
1822    function buildLightbox() {
1823        if (lb) return;
1824        lb = document.createElement('div'); lb.className = 'pg-lightbox'; lb.setAttribute('role', 'dialog');
1825        document.body.appendChild(lb);
1826        // Fullscreen viewer = universal physical layout: left button sits on the
1827        // left and points left (= previous), right sits right and points right
1828        // (= next), in every theme/locale. No direction logic — can't be reversed.
1829        lb.innerHTML =
1830            '<div class="pg-lb-stage">' +
1831                '<button type="button" class="pg-lb-btn pg-lb-close" aria-label="' + T.close + '">&times;</button>' +
1832                '<button type="button" class="pg-lb-btn pg-lb-nav pg-lb-prev" aria-label="' + T.prev + '">&#10094;</button>' +
1833                '<img class="pg-lb-img" alt="">' +
1834                '<button type="button" class="pg-lb-btn pg-lb-nav pg-lb-next" aria-label="' + T.next + '">&#10095;</button>' +
1835                '<div class="pg-lb-counter"></div>' +
1836                '<div class="pg-lb-tools">' +
1837                    '<button type="button" class="pg-lb-tool" data-pg-zoom="-1" aria-label="' + T.zoomOut + '" title="' + T.zoomOut + '">&minus;</button>' +
1838                    '<span class="pg-lb-zoomlabel">100%</span>' +
1839                    '<button type="button" class="pg-lb-tool" data-pg-zoom="1" aria-label="' + T.zoomIn + '" title="' + T.zoomIn + '">+</button>' +
1840                    '<a class="pg-lb-tool pg-lb-original" target="_blank" rel="noopener" aria-label="' + T.openOriginal + '" title="' + T.openOriginal + '">&#8599;</a>' +
1841                '</div>' +
1842                '<div class="pg-lb-caption"></div>' +
1843            '</div>';
1844        lbImg = lb.querySelector('.pg-lb-img');
1845        lbCaption = lb.querySelector('.pg-lb-caption');
1846        lbCounter = lb.querySelector('.pg-lb-counter');
1847        lb.querySelector('.pg-lb-close').addEventListener('click', close);
1848        lb.querySelector('.pg-lb-prev').addEventListener('click', function (e) { e.stopPropagation(); step(-1); });
1849        lb.querySelector('.pg-lb-next').addEventListener('click', function (e) { e.stopPropagation(); step(1); });
1850        lb.addEventListener('click', function (e) { if (e.target === lb || e.target.classList.contains('pg-lb-stage')) close(); });
1851        lb.addEventListener('touchstart', function (e) { lbTouchX = e.changedTouches[0].clientX; }, { passive: true });
1852        lb.addEventListener('touchend', function (e) {
1853            // A zoomed image is being panned, not swiped between photos.
1854            if (lbZoom > 1) return;
1855            var d = e.changedTouches[0].clientX - lbTouchX;
1856            if (Math.abs(d) > 45) step(d < 0 ? 1 : -1);
1857        });
1858        document.addEventListener('keydown', function (e) {
1859            if (!lb.classList.contains('is-open')) return;
1860            if (e.key === 'Escape') close();
1861            else if (e.key === 'ArrowRight') step(1);
1862            else if (e.key === 'ArrowLeft') step(-1);
1863            else if (e.key === '+' || e.key === '=') setZoom(lbZoom + .25);
1864            else if (e.key === '-') setZoom(lbZoom - .25);
1865            else if (e.key === '0') setZoom(1);
1866        });
1867
1868        // Zoom controls, the wheel, and double-click-to-toggle.
1869        lb.addEventListener('click', function (e) {
1870            var z = e.target.closest && e.target.closest('[data-pg-zoom]');
1871            if (!z) return;
1872            e.stopPropagation();
1873            setZoom(lbZoom + (parseFloat(z.getAttribute('data-pg-zoom')) * .25));
1874        });
1875        lb.addEventListener('wheel', function (e) {
1876            if (!lb.classList.contains('is-open')) return;
1877            e.preventDefault();
1878            setZoom(lbZoom + (e.deltaY < 0 ? .2 : -.2));
1879        }, { passive: false });
1880        lbImg.addEventListener('dblclick', function (e) {
1881            e.stopPropagation();
1882            setZoom(lbZoom > 1 ? 1 : 2);
1883        });
1884
1885        // Pan a zoomed image by dragging it.
1886        var panning = false, panX = 0, panY = 0;
1887        lbImg.addEventListener('pointerdown', function (e) {
1888            if (lbZoom <= 1) return;
1889            panning = true; panX = e.clientX - lbPanX; panY = e.clientY - lbPanY;
1890            try { lbImg.setPointerCapture(e.pointerId); } catch (err) {}
1891        });
1892        lbImg.addEventListener('pointermove', function (e) {
1893            if (!panning) return;
1894            lbPanX = e.clientX - panX; lbPanY = e.clientY - panY;
1895            applyZoom();
1896        });
1897        ['pointerup', 'pointercancel'].forEach(function (evt) {
1898            lbImg.addEventListener(evt, function () { panning = false; });
1899        });
1900    }
1901
1902    /* Zoom state lives beside the viewer, reset on every image change so a
1903       reader never lands on the next photo already scaled and off-centre. */
1904    var lbZoom = 1, lbPanX = 0, lbPanY = 0;
1905    function applyZoom() {
1906        if (!lbImg) return;
1907        lbImg.style.transform = 'translate(' + lbPanX + 'px,' + lbPanY + 'px) scale(' + lbZoom + ')';
1908        lbImg.classList.toggle('is-zoomed', lbZoom > 1);
1909        var label = lb && lb.querySelector('.pg-lb-zoomlabel');
1910        if (label) label.textContent = Math.round(lbZoom * 100) + '%';
1911    }
1912    function setZoom(z) {
1913        lbZoom = Math.min(4, Math.max(1, Math.round(z * 100) / 100));
1914        if (lbZoom === 1) { lbPanX = 0; lbPanY = 0; }
1915        applyZoom();
1916    }
1917    function showAt(i) {
1918        lbIndex = (i + lbList.length) % lbList.length;
1919        var src = lbList[lbIndex];
1920        // Reset zoom per image: landing on the next photo already scaled and
1921        // panned off-centre is disorienting.
1922        setZoom(1);
1923        lbImg.src = src.full;
1924        lbImg.alt = src.alt || '';
1925        var orig = lb.querySelector('.pg-lb-original');
1926        if (orig) orig.setAttribute('href', src.full);
1927        lbCaption.textContent = src.caption || '';
1928        lbCaption.style.display = src.caption ? '' : 'none';
1929        lbCounter.textContent = lbList.length > 1 ? (lbIndex + 1) + ' / ' + lbList.length : '';
1930        lb.querySelector('.pg-lb-prev').style.display = lbList.length > 1 ? '' : 'none';
1931        lb.querySelector('.pg-lb-next').style.display = lbList.length > 1 ? '' : 'none';
1932    }
1933    function step(d) { showAt(lbIndex + d); }
1934    function open(list, i) {
1935        buildLightbox();
1936        lbList = list; showAt(i);
1937        lb.classList.add('is-open');
1938        document.documentElement.style.overflow = 'hidden';
1939    }
1940    function close() {
1941        if (!lb) return;
1942        lb.classList.remove('is-open');
1943        document.documentElement.style.overflow = '';
1944        lbImg.src = '';
1945    }
1946
1947    // Build the eligible-image list (deduped, DOM order) and open at the clicked one.
1948    function openFromImage(target) {
1949        var nodes = Array.prototype.slice.call(document.querySelectorAll(IMG_SELECTOR)).filter(isLightboxImage);
1950        var seen = [], list = [], startIndex = 0, found = false;
1951        nodes.forEach(function (img) {
1952            if (!img.src || seen.indexOf(img) !== -1) return;
1953            seen.push(img);
1954            // Caption sources, in order: the gallery caption authored on the
1955            // node itself, then a sibling <figcaption> for ordinary article
1956            // figures.
1957            var cap = mediaCaption(img);
1958            if (!cap) {
1959                var fig = img.closest('figure');
1960                if (fig) { var fc = fig.querySelector('figcaption'); if (fc && !fc.hasAttribute('data-caption-placeholder')) cap = fc.textContent.trim(); }
1961            }
1962            list.push({ full: img.getAttribute('src'), alt: img.alt || '', caption: cap });
1963            if (img === target) { startIndex = list.length - 1; found = true; }
1964        });
1965        if (list.length) open(list, found ? startIndex : 0);
1966    }
1967
1968    // One delegated listener handles carousel controls AND lightbox-eligible
1969    // images (now and future) — survives any later DOM re-render.
1970    document.addEventListener('click', function (e) {
1971        var t = e.target;
1972        if (!t || !t.closest) return;
1973        var goBtn = t.closest('[data-pg-go]');
1974        if (goBtn) {
1975            var g = goBtn.closest('[data-post-gallery]');
1976            if (g) { e.preventDefault(); moveCarousel(g, goBtn.getAttribute('data-pg-go') === 'next' ? 1 : -1); }
1977            return;
1978        }
1979        // "+N" badge: open the viewer at the first item the grid did not draw.
1980        var moreBtn = t.closest('[data-pg-more]');
1981        if (moreBtn) {
1982            e.preventDefault();
1983            var gm = moreBtn.closest('[data-post-gallery]');
1984            var imgs = gm ? gm.querySelectorAll('img') : [];
1985            var at = parseInt(moreBtn.getAttribute('data-pg-more'), 10) || 0;
1986            if (imgs[at]) openFromImage(imgs[at]);
1987            return;
1988        }
1989        var dotBtn = t.closest('[data-pg-dot]');
1990        if (dotBtn) {
1991            var g2 = dotBtn.closest('[data-post-gallery]');
1992            if (g2) { e.preventDefault(); g2.setAttribute('data-pg-index', dotBtn.getAttribute('data-pg-dot')); applyCarousel(g2); }
1993            return;
1994        }
1995        var img = t.closest('img');
1996        if (isLightboxImage(img)) { e.preventDefault(); openFromImage(img); }
1997    });
1998
1999    // Swipe — also delegated, for the same resilience.
2000    var swGal = null, swX = 0, swDX = 0;
2001    document.addEventListener('touchstart', function (e) {
2002        var v = e.target.closest ? e.target.closest('.pg-viewport') : null;
2003        swGal = v ? v.closest('[data-post-gallery]') : null;
2004        if (swGal) { swX = e.touches[0].clientX; swDX = 0; }
2005    }, { passive: true });
2006    document.addEventListener('touchmove', function (e) { if (swGal) swDX = e.touches[0].clientX - swX; }, { passive: true });
2007    document.addEventListener('touchend', function () {
2008        if (swGal && Math.abs(swDX) > 40) moveCarousel(swGal, (swDX < 0) !== elRTL(swGal) ? 1 : -1);
2009        swGal = null;
2010    });
2011
2012    /* ----------------------------- Boot ----------------------------- */
2013    function boot() { try { enhanceGalleries(document); } catch (e) {} }
2014    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
2015    else boot();
2016    // Re-enhance content rendered later (Livewire navigations, lazy sections).
2017    if (window.MutationObserver) {
2018        var mo = new MutationObserver(function (muts) {
2019            for (var i = 0; i < muts.length; i++) {
2020                if (muts[i].addedNodes && muts[i].addedNodes.length) { enhanceGalleries(document); break; }
2021            }
2022        });
2023        try { mo.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
2024    }
2025    document.addEventListener('livewire:navigated', boot);
2026})();
2027</script>
2027
2028
2029
2030<div class="qd-modal" id="qdMediaModal" hidden>
2031    <div class="qd-modal__backdrop" data-media-close></div>
2032
2033    <div class="qd-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="qdMediaTitle">
2034        
2035        <div class="qd-modal__bar">
2036            <button type="button" class="qd-modal__close" data-media-close aria-label="إغلاق">
2037                <i class="fa-solid fa-xmark" aria-hidden="true"></i>
2038            </button>
2039
2040            
2041            <div class="qd-modal__nav" id="qdMediaNav" hidden>
2042                <button type="button" class="qd-modal__step" data-media-prev aria-label="السابق">
2043                    <i class="fa-solid fa-chevron-right" aria-hidden="true"></i>
2044                </button>
2045                <span class="qd-modal__count" id="qdMediaCount"></span>
2046                <button type="button" class="qd-modal__step" data-media-next aria-label="التالي">
2047                    <i class="fa-solid fa-chevron-left" aria-hidden="true"></i>
2048                </button>
2049            </div>
2050        </div>
2051
2052        <div class="qd-modal__stage">
2053            <div class="qd-modal__player" id="qdMediaPlayer"></div>
2054
2055            
2056            <span class="qd-modal__rail qd-modal__rail--start" data-media-swipe aria-hidden="true"></span>
2057            <span class="qd-modal__rail qd-modal__rail--end" data-media-swipe aria-hidden="true"></span>
2058        </div>
2059
2060        
2061        <div class="qd-modal__updown" id="qdMediaUpDown" hidden>
2062            <button type="button" class="qd-modal__step" data-media-prev aria-label="السابق">
2063                <i class="fa-solid fa-chevron-up" aria-hidden="true"></i>
2064            </button>
2065            <span class="qd-modal__count" id="qdMediaCountV"></span>
2066            <button type="button" class="qd-modal__step" data-media-next aria-label="التالي">
2067                <i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
2068            </button>
2069        </div>
2070
2071        <div class="qd-modal__body">
2072            <h2 class="qd-modal__title" id="qdMediaTitle"></h2>
2073            <p class="qd-modal__desc" id="qdMediaDesc"></p>
2074            
2075            <button type="button" class="qd-modal__more" id="qdMediaMore" hidden>
2076                <span>عرض المزيد</span><i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
2077            </button>
2078        </div>
2079    </div>
2080</div>
2081
2082<style>
2083    .qd-modal { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 20px; }
2084    .qd-modal[hidden] { display: none; }
2085    .qd-modal__backdrop { position: absolute; inset: 0; background: rgba(8, 14, 24, .72); backdrop-filter: blur(3px); }
2086    .qd-modal__dialog { position: relative; width: min(880px, 100%); max-height: 90vh; overflow-y: auto;
2087        background: var(--bg, #fff); border-radius: var(--r-lg, 16px); box-shadow: 0 30px 80px rgba(0, 0, 0, .35); }
2088    /* Its own strip, so nothing of ours sits on the player's controls. */
2089    .qd-modal__bar { display: flex; justify-content: space-between; align-items: center; gap: 10px;
2090        padding: 8px 10px; background: #0d1117; border-radius: var(--r-lg, 16px) var(--r-lg, 16px) 0 0; }
2091    .qd-modal__close, .qd-modal__step { width: 36px; height: 36px; border: 0; border-radius: 50%; cursor: pointer;
2092        background: rgba(255, 255, 255, .12); color: #fff; font-size: 16px; display: grid; place-items: center;
2093        transition: background .2s; }
2094    .qd-modal__close:hover, .qd-modal__step:hover:not(:disabled) { background: rgba(255, 255, 255, .25); }
2095    .qd-modal__step:disabled { opacity: .3; cursor: default; }
2096    .qd-modal__nav { display: flex; align-items: center; gap: 8px; }
2097    .qd-modal__count { color: rgba(255, 255, 255, .65); font-size: 12.5px; font-weight: 700;
2098        font-variant-numeric: tabular-nums; min-width: 52px; text-align: center; }
2099    .qd-modal__player { background: #000; overflow: hidden; }
2100    .qd-modal__player iframe, .qd-modal__player video { display: block; width: 100%; border: 0; }
2101    /* Video keeps 16:9; an audio player is short and must not be stretched. */
2102    .qd-modal__player[data-kind="youtube"] iframe,
2103    .qd-modal__player[data-kind="iframe"] iframe { aspect-ratio: 16 / 9; height: auto; }
2104    .qd-modal__stage { position: relative; }
2105    .qd-modal__rail { display: none; }
2106    .qd-modal__updown { display: none; }
2107
2108    /* ── REEL VIEWER ──────────────────────────────────────────────────────
2109       A reel is the whole thing: no card around it, no copy under it. The
2110       dialog becomes the video's own 9:16 frame, sized to the viewport so it
2111       never scrolls, with the controls floating clear of it. */
2112    .qd-modal__dialog[data-ratio="vertical"] {
2113        width: auto; max-height: none; overflow: visible;
2114        background: transparent; box-shadow: none;
2115        display: flex; align-items: center; gap: 14px;
2116    }
2117    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__bar {
2118        position: absolute; inset-block-start: -52px; inset-inline-end: 0;
2119        background: transparent; padding: 0; z-index: 3;
2120    }
2121    /* The horizontal pager belongs to the wide player; a reel gets the
2122       up/down column beside it instead. */
2123    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__nav,
2124    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__body { display: none; }
2125    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__updown {
2126        display: flex; flex-direction: column; align-items: center; gap: 12px;
2127    }
2128    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__stage {
2129        /* 9:16, bounded by the shorter of the two axes so the frame always fits. */
2130        height: min(86vh, calc((100vw - 140px) * 16 / 9));
2131        aspect-ratio: 9 / 16;
2132        border-radius: 18px; overflow: hidden; background: #000;
2133        box-shadow: 0 30px 80px rgba(0, 0, 0, .5);
2134    }
2135    /* The iframe is exactly the frame — no scaling, no clipping. An earlier
2136       version built it 16:9 at full height and let it overflow sideways, to
2137       trade YouTube's letterboxing for a fuller picture; it magnified the video
2138       instead and cut the captions burnt into it off both edges. Whatever
2139       YouTube pads around a Short is better than showing part of it. */
2140    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__player { height: 100%; }
2141    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__player[data-kind="youtube"] iframe,
2142    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__player[data-kind="iframe"] iframe {
2143        width: 100%; height: 100%; aspect-ratio: auto;
2144    }
2145    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__rail {
2146        display: block; position: absolute; inset-block: 0; width: 56px; z-index: 2;
2147        touch-action: pan-y;
2148    }
2149    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__rail--start { inset-inline-start: 0; }
2150    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__rail--end { inset-inline-end: 0; }
2151    /* «5 / 12», not «12 / 5»: the slash makes RTL reorder the two numbers. */
2152    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__count {
2153        min-width: 0; font-size: 12px; direction: ltr;
2154    }
2155    .qd-modal__dialog[data-ratio="vertical"] .qd-modal__step {
2156        width: 44px; height: 44px; font-size: 17px;
2157        background: rgba(255, 255, 255, .14); backdrop-filter: blur(6px);
2158    }
2159
2160    /* On a phone the reel takes the screen and everything of ours floats on top
2161       of it: there is no room for a bar above or a column beside. */
2162    @media (max-width: 560px) {
2163        .qd-modal { padding: 0; }
2164        .qd-modal__dialog[data-ratio="vertical"] { gap: 0; width: 100%; }
2165        /* The height decides the width here — aspect-ratio derives it — so the
2166           modal's own 12px padding has to come out of the height, or the frame
2167           ends up 24px wider than the space it sits in and hangs off the left. */
2168        .qd-modal__dialog[data-ratio="vertical"] .qd-modal__stage {
2169            width: auto;
2170            height: min(calc(100dvh - 24px), calc((100vw - 24px) * 16 / 9));
2171            border-radius: 12px;
2172        }
2173        /* Above the frame there is only the browser's own chrome, so the close
2174           button came to rest off-screen. It sits inside the frame now. */
2175        .qd-modal__dialog[data-ratio="vertical"] .qd-modal__bar {
2176            inset-block-start: 10px; inset-inline-end: 10px; z-index: 4;
2177        }
2178        .qd-modal__dialog[data-ratio="vertical"] .qd-modal__close {
2179            background: rgba(0, 0, 0, .55); backdrop-filter: blur(6px);
2180        }
2181        .qd-modal__dialog[data-ratio="vertical"] .qd-modal__updown {
2182            position: absolute; inset-inline-start: 10px; inset-block-start: 50%;
2183            transform: translateY(-50%); z-index: 3; gap: 10px;
2184        }
2185        .qd-modal__dialog[data-ratio="vertical"] .qd-modal__step { width: 40px; height: 40px; }
2186        /* Narrower rails: on a phone the arrows already occupy one side, and a
2187           56px strip over a 375px screen would swallow a third of the video. */
2188        .qd-modal__dialog[data-ratio="vertical"] .qd-modal__rail { width: 40px; }
2189    }
2190    .qd-modal__player[data-kind="soundcloud"] { background: transparent; }
2191    .qd-modal__player[data-kind="soundcloud"] iframe { height: 166px; }
2192    .qd-modal__player[data-kind="file"] { background: transparent; padding: 18px 20px 0; }
2193    /* An uploaded clip plays in place, letterboxed like the embeds above it. */
2194    .qd-modal__player[data-kind="video"] video { display: block; width: 100%; max-height: 78vh; background: #000; }
2195    .qd-modal__body { padding: 20px 24px 26px; }
2196    .qd-modal__title { margin: 0 0 10px; font-size: clamp(17px, 2vw, 21px); font-weight: 800; line-height: 1.6; color: var(--ink, #15181d); }
2197    .qd-modal__desc { margin: 0; color: var(--muted, #5b626e); line-height: 1.95; font-size: 15px; white-space: pre-line; }
2198    .qd-modal__desc:empty { display: none; }
2199    .qd-modal__more { display: none; }
2200    body.qd-modal-open { overflow: hidden; }
2201
2202    /* On a phone the episode blurb ran longer than the video itself, pushing the
2203       player off-screen the moment the modal opened. Collapsed to a few lines
2204       with a toggle; the class only ever goes on when the text really overflows,
2205       so short descriptions get no button. */
2206    @media (max-width: 640px) {
2207        .qd-modal { padding: 12px; }
2208        .qd-modal__body { padding: 16px 18px 20px; }
2209        .qd-modal__desc.is-clamped {
2210            display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden;
2211        }
2212        .qd-modal__more {
2213            display: inline-flex; align-items: center; gap: 6px; margin-top: 10px;
2214            padding: 7px 14px; border: 1px solid var(--line, #e3e6ea); border-radius: 999px;
2215            background: transparent; color: var(--accent, #2f7fcf);
2216            font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
2217        }
2218        .qd-modal__more i { font-size: 11px; transition: transform .2s; }
2219        .qd-modal__more[aria-expanded="true"] i { transform: rotate(180deg); }
2220    }
2221</style>
2222
2223<script>
2224    (function () {
2225        var modal = document.getElementById('qdMediaModal');
2226        if (!modal) return;
2227
2228        var player = document.getElementById('qdMediaPlayer');
2229        var titleEl = document.getElementById('qdMediaTitle');
2230        var descEl = document.getElementById('qdMediaDesc');
2231        var moreBtn = document.getElementById('qdMediaMore');
2232        var dialog = modal.querySelector('.qd-modal__dialog');
2233        var navEl = document.getElementById('qdMediaNav');
2234        var upDownEl = document.getElementById('qdMediaUpDown');
2235        var countEls = [document.getElementById('qdMediaCount'), document.getElementById('qdMediaCountV')];
2236        var prevBtns = [].slice.call(modal.querySelectorAll('[data-media-prev]'));
2237        var nextBtns = [].slice.call(modal.querySelectorAll('[data-media-next]'));
2238        var narrow = window.matchMedia('(max-width: 640px)');
2239        var lastFocus = null;
2240
2241        // The open trigger's siblings, so «التالي» walks the grid the reader
2242        // was looking at rather than every player on the page.
2243        var siblings = [];
2244        var index = -1;
2245
2246        /**
2247         * Collect the playable cards around a trigger.
2248         *
2249         * Scoped to the nearest list container — a swiper wrapper, a grid — so a
2250         * reel opened from the video rail steps through that rail. Swiper clones
2251         * its slides for looping, and a clone carries the same attributes as the
2252         * original, so they are filtered out or the reader would meet the same
2253         * video twice.
2254         */
2255        function collectSiblings(trigger) {
2256            var scope = trigger.closest('.swiper-wrapper, .cards-grid, .video-grid, .section, .container') || document;
2257            var all = [].slice.call(scope.querySelectorAll('[data-media-open][data-media-src]'));
2258
2259            siblings = all.filter(function (el) {
2260                return !el.closest('.swiper-slide-duplicate');
2261            });
2262
2263            index = siblings.indexOf(trigger);
2264            if (index === -1 && siblings.length) {
2265                // A duplicate slide was clicked: match the original by source.
2266                var src = trigger.getAttribute('data-media-src');
2267                index = siblings.findIndex(function (el) {
2268                    return el.getAttribute('data-media-src') === src;
2269                });
2270            }
2271
2272            var many = siblings.length > 1 && index > -1;
2273            navEl.hidden = !many;
2274            upDownEl.hidden = !many;
2275            if (many) syncNav();
2276        }
2277
2278        /** Reflect the current position on both control sets. */
2279        function syncNav() {
2280            prevBtns.forEach(function (b) { b.disabled = index <= 0; });
2281            nextBtns.forEach(function (b) { b.disabled = index >= siblings.length - 1; });
2282            countEls.forEach(function (el) {
2283                if (el) el.textContent = (index + 1) + ' / ' + siblings.length;
2284            });
2285        }
2286
2287        function step(delta) {
2288            var target = siblings[index + delta];
2289            if (!target) return;
2290            open(target, true);
2291        }
2292
2293        prevBtns.forEach(function (b) { b.addEventListener('click', function () { step(-1); }); });
2294        nextBtns.forEach(function (b) { b.addEventListener('click', function () { step(1); }); });
2295
2296        /* ── Gestures, for the reel viewer ────────────────────────────────
2297           A swipe up means «التالي», the way every reel feed behaves. The
2298           listeners live on the side rails because the player's iframe keeps
2299           every touch that lands on it; the wheel is read off the dialog,
2300           which does see it. */
2301        var touchY = null;
2302
2303        modal.addEventListener('touchstart', function (e) {
2304            if (!e.target.closest('[data-media-swipe]')) return;
2305            touchY = e.changedTouches[0].clientY;
2306        }, { passive: true });
2307
2308        modal.addEventListener('touchend', function (e) {
2309            if (touchY === null) return;
2310            var dy = e.changedTouches[0].clientY - touchY;
2311            touchY = null;
2312            if (Math.abs(dy) < 45) return;      // a tap, or a hesitation
2313            step(dy < 0 ? 1 : -1);              // swipe up → next
2314        }, { passive: true });
2315
2316        // One notch of a trackpad can fire dozens of events; ignore the tail.
2317        var wheelLock = false;
2318        modal.addEventListener('wheel', function (e) {
2319            if (dialog.getAttribute('data-ratio') !== 'vertical') return;
2320            if (Math.abs(e.deltaY) < 20 || wheelLock) return;
2321            wheelLock = true;
2322            setTimeout(function () { wheelLock = false; }, 600);
2323            step(e.deltaY > 0 ? 1 : -1);
2324        }, { passive: true });
2325
2326        /**
2327         * Collapse the description if it is long enough to be worth collapsing.
2328         * Measured after the modal is visible — a hidden element reports zero
2329         * height, which would clamp everything or nothing.
2330         */
2331        function setupClamp() {
2332            descEl.classList.remove('is-clamped');
2333            moreBtn.hidden = true;
2334            moreBtn.setAttribute('aria-expanded', 'false');
2335            moreBtn.querySelector('span').textContent = 'عرض المزيد';
2336
2337            if (!narrow.matches || !descEl.textContent.trim()) return;
2338
2339            descEl.classList.add('is-clamped');
2340            if (descEl.scrollHeight - descEl.clientHeight > 4) {
2341                moreBtn.hidden = false;
2342            } else {
2343                descEl.classList.remove('is-clamped');
2344            }
2345        }
2346
2347        moreBtn.addEventListener('click', function () {
2348            var expanded = descEl.classList.toggle('is-clamped') === false;
2349            moreBtn.setAttribute('aria-expanded', String(expanded));
2350            moreBtn.querySelector('span').textContent = expanded ? 'عرض أقل' : 'عرض المزيد';
2351        });
2352
2353        function close() {
2354            modal.hidden = true;
2355            document.body.classList.remove('qd-modal-open');
2356            // Emptying the container stops playback — a paused iframe would keep
2357            // running audio behind the closed modal.
2358            player.innerHTML = '';
2359            player.removeAttribute('data-kind');
2360            if (lastFocus && lastFocus.focus) lastFocus.focus();
2361        }
2362
2363        /**
2364         * Start playing as soon as the player appears.
2365         *
2366         * The modal only ever opens from a click, so this is the reader asking
2367         * to watch — they should not have to ask twice. `playsinline` keeps a
2368         * phone from taking the video fullscreen on its own, and `rel=0` stops
2369         * YouTube ending on a wall of other channels' videos.
2370         *
2371         * A browser that refuses unmuted autoplay simply shows YouTube's own
2372         * play button, which is exactly where this started — no regression.
2373         */
2374        function withAutoplay(src, kind) {
2375            if (kind !== 'youtube' && kind !== 'iframe') return src;
2376            if (src.indexOf('autoplay=') !== -1) return src;
2377
2378            return src + (src.indexOf('?') === -1 ? '?' : '&') + 'autoplay=1&playsinline=1&rel=0';
2379        }
2380
2381        function open(trigger, stepping) {
2382            var kind = trigger.getAttribute('data-media-kind') || 'iframe';
2383            var src = trigger.getAttribute('data-media-src') || '';
2384            if (!src) return false;
2385
2386            if (stepping) {
2387                index = siblings.indexOf(trigger);
2388                syncNav();
2389            } else {
2390                lastFocus = trigger;
2391                collectSiblings(trigger);
2392            }
2393
2394            // A reel is vertical; the dialog follows the video's shape.
2395            dialog.setAttribute('data-ratio',
2396                trigger.getAttribute('data-media-ratio') === 'vertical' ? 'vertical' : 'wide');
2397
2398            player.setAttribute('data-kind', kind);
2399
2400            if (kind === 'file') {
2401                var audio = document.createElement('audio');
2402                audio.src = src;
2403                audio.controls = true;
2404                audio.style.width = '100%';
2405                player.innerHTML = '';
2406                player.appendChild(audio);
2407            } else if (kind === 'video') {
2408                // An uploaded clip — a podcast episode filmed rather than
2409                // recorded, or a local video material. An <iframe> would show
2410                // the browser's bare file viewer instead of our player.
2411                var video = document.createElement('video');
2412                video.src = src;
2413                video.controls = true;
2414                video.autoplay = true;
2415                video.playsInline = true;
2416                video.preload = 'metadata';
2417                player.innerHTML = '';
2418                player.appendChild(video);
2419            } else {
2420                var frame = document.createElement('iframe');
2421                frame.src = withAutoplay(src, kind);
2422                frame.setAttribute('allow', 'autoplay; accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
2423                frame.setAttribute('allowfullscreen', '');
2424                // Not lazy: this frame is created because the reader just asked
2425                // for it, and a deferred load is a beat of nothing on screen.
2426                frame.setAttribute('loading', 'eager');
2427                frame.setAttribute('title', trigger.getAttribute('data-media-title') || '');
2428                if (kind === 'soundcloud') frame.setAttribute('scrolling', 'no');
2429                player.innerHTML = '';
2430                player.appendChild(frame);
2431            }
2432
2433            titleEl.textContent = trigger.getAttribute('data-media-title') || '';
2434            descEl.textContent = trigger.getAttribute('data-media-desc') || '';
2435
2436            modal.hidden = false;
2437            document.body.classList.add('qd-modal-open');
2438            setupClamp();
2439            modal.querySelector('.qd-modal__close').focus();
2440            return true;
2441        }
2442
2443        document.addEventListener('click', function (e) {
2444            if (e.target.closest('[data-media-close]')) { close(); return; }
2445
2446            var trigger = e.target.closest('[data-media-open]');
2447            if (!trigger) return;
2448
2449            // Only swallow the navigation if a player actually opened, so a card
2450            // with nothing playable still follows its link.
2451            if (open(trigger)) e.preventDefault();
2452        });
2453
2454        document.addEventListener('keydown', function (e) {
2455            if (modal.hidden) return;
2456            if (e.key === 'Escape') { close(); return; }
2457
2458            // A reel is a vertical feed, so it answers to Up/Down; the wide
2459            // player pages sideways. RTL puts «التالي» on the left.
2460            if (dialog.getAttribute('data-ratio') === 'vertical') {
2461                if (e.key === 'ArrowDown') { e.preventDefault(); step(1); }
2462                if (e.key === 'ArrowUp')   { e.preventDefault(); step(-1); }
2463                return;
2464            }
2465            if (e.key === 'ArrowLeft')  { step(1); }
2466            if (e.key === 'ArrowRight') { step(-1); }
2467        });
2468
2469        // Leaving the page via SPA navigation must not leave a player running.
2470        document.addEventListener('livewire:navigating', close);
2471    })();
2472</script>
2472
2473
2474
2475<script>
2476    (function () {
2477        if (!document.querySelector('blockquote.twitter-tweet')) return;
2478        if (document.getElementById('twitter-wjs')) return;
2479        var s = document.createElement('script');
2480        s.id = 'twitter-wjs';
2481        s.async = true;
2482        s.charset = 'utf-8';
2483        s.src = 'https://platform.twitter.com/widgets.js';
2484        document.body.appendChild(s);
2485    })();
2486</script>
vendor: 1 bytes, line 2486
2486
2487<script src="https://qudsn.co/vendor/livewire/livewire.min.js?id=f4724cc4"   data-csrf="Y0gik7cwXceZDdqSc3S7sjrWhvqOy61dNGTi6Jk4" data-module-url="https://qudsn.co/livewire-dcd59c78" data-update-uri="https://qudsn.co/livewire-dcd59c78/update" data-navigate-once="true"></script>
vendor: 1 bytes, line 2487
2487
2488<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"afc00504e7c949c9b1d808ce2fc6fc19","r":1,"spa":2}' crossorigin="anonymous"></script>
2488
2489</body>
2490
2491</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.