PageSourceSearch

https://qistas.com/assets/js/site.js?v=1790849444

js qistas.com collected 2026-10-01 12:36:26 UTC 36,943 bytes, 563 lines download raw bytes

1/* =============================================================================
2   Qistas — site behaviour
3   Requires: jQuery 3.7 (loaded first), Bootstrap 5.3 bundle (components/utilities).
4   The site ships one page tree per language (en/ + ar/), so the interface language
5   is read once from <html lang> — there is no runtime string swapping.
6   ========================================================================== */
7(function ($) {
8  'use strict';
9
10  var LANG = document.documentElement.getAttribute('lang') === 'ar' ? 'ar' : 'en';
11  var reduce = window.matchMedia && matchMedia('(prefers-reduced-motion:reduce)').matches;
12
13  /* ---------------------------------------------------------------- icon set */
14  var I={
15    search:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>',
16    chat:'<span class="pico pico-chat"></span>',
17    gavel:'<span class="pico pico-gavel"></span>',
18    doc:'<span class="pico pico-doc"></span>',
19    bank:'<span class="pico pico-bank"></span>',
20    bolt:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h7l-1 8 10-12h-7z"/></svg>',
21    home:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 11.5 12 4l8 7.5"/><path d="M6 10v8.5a1 1 0 0 0 1 1h3v-6h4v6h3a1 1 0 0 0 1-1V10"/></svg>',
22    globe:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>',
23    chevronDown:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',
24    folder:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>',
25    clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
26    bell:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>',
27    mail:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>',
28    sun:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>',
29    userTop:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>',
30
31    sparkle:'<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 3c.4 3.2 1 5.2 2 6.2S17 11 20 11c-3 .4-5 1-6 2s-1.6 2.8-2 6c-.4-3.2-1-5.2-2-6S6 11.4 4 11c3-.4 5-1 6-2s1.6-2.8 2-6Z"/></svg>',
32    crosshair:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="2" x2="5" y1="12" y2="12"/><line x1="19" x2="22" y1="12" y2="12"/><line x1="12" x2="12" y1="2" y2="5"/><line x1="12" x2="12" y1="19" y2="22"/><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="3"/></svg>',
33    key:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></svg>',
34    sparklePlus:'<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>',
35    mic:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>',
36    wand:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M5 19 15.5 8.5"/><path d="M18 4l.7 1.8L20.5 6.5l-1.8.7L18 9l-.7-1.8-1.8-.7 1.8-.7Z"/>
36<path d="M6.5 14.5l.4 1 1 .4-1 .4-.4 1-.4-1-1-.4 1-.4Z"/></svg>',
37    docnav:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="3.5" width="12" height="17" rx="2"/><path d="M9 8h6M9 12h6M9 16h3.5"/></svg>',
38    book:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5.5A2 2 0 016 4h6v16H6a2 2 0 01-2-1.5z"/><path d="M20 5.5A2 2 0 0018 4h-6v16h6a2 2 0 002-1.5z"/></svg>'
39  };
40
41  /* --------------------------------------- research-home mockup copy (bilingual) */
42  var RH={
43    en:{hello:'Hello,',h:'What would you like to research today?',ph:'Search by document name, citation, keyword, or legal topic',go:'Search',
44      chips:['All Documents','Legislation','Cases','Treaties','Constitutional','Reports','Seminal Cases'],
45      tabs:['Legal News','Legal Updates','Legal Content','Practice Areas','Resource Centre','Our Solutions'],
46      news:[['New Federal Decree-Law on Commercial Companies Amendment','UAE · Corporate Law · March 2026','Enhanced corporate governance requirements introduced to the Commercial Companies Law.'],
47            ['Supreme Court Ruling on Digital Evidence Admissibility','UAE · Civil Law · March 2026','New standards established for accepting digital evidence in civil proceedings.'],
48            ['GCC Trade Agreement Updates, New Provisions','GCC · International Trade · March 2026','Member states ratify amendments affecting cross-border rules.'],
49            ['Amendments to Labour Law: Remote Work Provisions','UAE · Employment Law · Feb 2026','Updated provisions covering remote and hybrid working.']]},
50    ar:{hello:'مرحباً،',h:'ماذا تريــد أن تبحــث اليــوم ؟',ph:'ابحث باسم المستند أو الاستشهاد أو كلمة مفتاحية أو موضوع قانوني',go:'بحث',
51      chips:['كل المستندات','التشريعات','الأحكام','المعاهدات','الدستوري','التقارير','قضايا مرجعية'],
52      tabs:['الأخبار القانونية','التحديثات القانونية','المحتوى القانوني','مجالات الممارسة','مركز الموارد','حلولنا'],
53      news:[['مرسوم بقانون اتحادي جديد بتعديل قانون الشركات التجارية','الإمارات · قانون الشركات · مارس 2026','إدخال متطلبات معزّزة للحوكمة المؤسسية على قانون الشركات التجارية.'],
54            ['حكم من المحكمة العليا بشأن قبول الأدلة الرقمية','الإمارات · القانون المدني · مارس 2026','إرساء معايير جديدة لقبول الأدلة الرقمية في الدعاوى المدنية.'],
55            ['تحديثات اتفاقية التجارة الخليجية، أحكام جديدة','دول الخليج · التجارة الدولية · مارس 2026','مصادقة الدول الأعضاء على تعديلات تمس القواعد العابرة للحدود.'],
56            ['تعديلات على قانون العمل: أحكام العمل عن بُعد','الإمارات · قانون العمل · فبراير 2026','أحكام محدّثة تغطي العمل عن بُعد والعمل المختلط.']]}
57  };
58
59  var SIDE={en:{home:'Home',search:'Search',ai:'AI Assistants',drafting:'Drafting',documents:'Documents',library:'Library'},
60    ar:{home:'الرئيسية',search:'بحث',ai:'المساعدون الأذكياء',drafting:'الصياغة',documents:'المستندات',library:'المكتبة'}};
61
62  /* assistant verticals as they appear in the mockup sidebar */
63  var ASST = [
64    { key: 'research',    ic: I.chat,  tab: { en: 'Research',    ar: 'البحث' } },
65    { key: 'litigation',  ic: I.gavel, tab: { en: 'Litigation',  ar: 'التقاضي' } },
66    { key: 'contracts',   ic: I.doc,   tab: { en: 'Contracts',   ar: 'العقود' } },
67    { key: 'legislative', ic: I.bank,  tab: { en: 'Legislative', ar: 'التشريع' } }
68  ];
69
70  /* ------------------------------------------------------------- hero starfield */
71  function seedStars(box, n, shoots, bright) {
72    var f = '';
73    for (var i = 0; i < n; i++) {
74      var s = bright ? (Math.random() * 2.4 + 1.2).toFixed(2) : (Math.random() * 1.7 + 0.5).toFixed(2);
75      var x = (Math.random() * 100).toFixed(2), y = (Math.random() * 88).toFixed(2);
76      var dur = bright ? (Math.random() * 2.4 + 1.8).toFixed(2) : (Math.random() * 4 + 3).toFixed(2);
77      var del = (Math.random() * 5).toFixed(2);
78      var op = bright ? (Math.random() * .35 + .55).toFixed(2) : (Math.random() * .5 + .2).toFixed(2);
79      f += '<span class="star' + (bright ? ' bright' : '') + '" style="width:' + s + 'px;height:' + s + 'px;left:' + x + '%;top:' + y + '%;opacity:' + op + ';animation:' + (bright ? 'twb' : 'tw') + ' ' + dur + 's ease-in-out ' + del + 's infinite"></span>';
80    }
81    for (var j = 0; j < shoots; j++) {
82      var st = (Math.random() * 30 + 3).toFixed(1), sl = (Math.random() * 76 + 3).toFixed(1);
83      var w = Math.round(54 + Math.random() * 30);
84      var sd = (18 + Math.random() * 12).toFixed(1), sdel = (Math.random() * 24).toFixed(1);
85      f += '<span class="shoot" style="top:' + st + '%;left:' + sl + '%;width:' + w + 'px;animation:shoot ' + sd + 's linear ' + sdel + 's infinite"></span>';
86    }
87    box.innerHTML = f;
88  }
89
90  function initStars() {
91    var box = document.getElementById('stars');
92    if (box) seedStars(box, window.innerWidth < 640 ? 30 : 52, 0);
93    ['assistants', 'cta'].forEach(function (id) {
94      var sec = document.getElementById(id);
95      if (!sec) return;
96      var b = document.createElement('div');
97      b.className = 'stars';
98      sec.insertBefore(b, sec.firstChild);
99      var isAsst = id === 'assistants', isCta = id === 'cta';
100      seedStars(b, window.innerWidth < 640 ? (isAsst ? 12 : (isCta ? 20 : 16)) : (isAsst ? 18 : (isCta ? 30 : 26)), 1, isAsst || isCta);
101    });
102    document.querySelectorAll('.page-head').forEach(function (sec) {
103      var b = document.createElement('div');
104      b.className = 'stars';
105      sec.insertBefore(b, sec.firstChild);
106      seedStars(b, window.innerWidth < 640 ? 9 : 14, 0, false);
107    });
108  }
109
110  /* --------------------------------------------------------- reveal on scroll */
111  var revIO = new IntersectionObserver(function (es) {
112    es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); revIO.unobserve(e.target); } });
113  }, { threshold: .12 });
114  var truleIO = new IntersectionObserver(function (es) {
115    es.forEach(function (e) { e.target.classList.toggle('in', e.isIntersecting); });
116  }, { threshold: .2 });
117
118  function armReveals() { $('.reveal').not('.in').each(function () { revIO.observe(this); }); }
119  function armTrules() { $('.trule').each(function () { truleIO.observe(this); }); }
120  /* failsafe: never leave an in-viewport reveal stuck hidden */
121  function revealVisible() {
122    $('.reveal').not('.in').each(function () {
123      var r = this.getBoundingClientRect();
124      if (r.top < innerHeight * 0.95 && r.bottom > 0) { this.classList.add('in'); revIO.unobserve(this); }
125    });
126  }
127
128  /* ------------------------------------------------------------ number count-up */
129  function initCounters() {
130    var tgt = document.querySelector('.cband');
131    if (!tgt) return;
132    var fired = false;
133    function ease(t) { return 1 - Math.pow(1 - t, 3); }
134    function run() {
135      if (fired) return;
136      fired = true;
137      $('.cband .cnum').each(function () {
138        var el = this,
139            end = parseFloat(el.dataset.count),
140            dec = parseInt(el.dataset.dec || '0', 10),
141            suf = el.dataset.suffix || '';
142        if (reduce) { el.textContent = end.toFixed(dec) + suf; return; }
143        var t0 = null, dur = 1500;
144        function step(ts) {
145          if (t0 === null) t0 = ts;
146          var p = Math.min((ts - t0) / dur, 1);
147          el.textContent = (end * ease(p)).toFixed(dec) + suf;
148          if (p < 1) requestAnimationFrame(step); else el.textContent = end.toFixed(dec) + suf;
149        }
150        requestAnimationFrame(step);
151      });
152    }
153    if ('IntersectionObserver' in window) {
154      var io = new IntersectionObserver(function (es) {
155        es.forEach(function (e) { if (e.isIntersecting) { run(); io.disconnect(); } });
156      }, { threshold: .35 });
157      io.observe(tgt);
158    } else { run(); }
159  }
160
161  /* ------------------------------------------------------- rotating hero line */
162  var PHRASES={en:['Purpose build for Arabic legal systems and critical practice areas','and grounded in verified legal content and research'],
163    ar:['مصمّم خصيصاً للأنظمة القانونية العربية وأهم مجالات الممارسة','وبالاستناد إلى محتوى وبحث قانوني موثوق']};
164  function startTyper() {
165    var rot = document.getElementById('rot');
166    if (!rot) return;
167    var phrases = PHRASES[LANG], idx = 0, timer = 0;
168    rot.style.transition = 'opacity .5s';
169    rot.style.opacity = 1;
170    rot.textContent = phrases[0];
171    (function fade() {
172      timer = setTimeout(function () {
173        rot.style.opacity = 0;
174        timer = setTimeout(function () {
175          idx = (idx + 1) % phrases.length;
176          rot.textContent = phrases[idx];
177          rot.style.opacity = 1;
178          fade();
179        }, 500);
180      }, 2600);
181    })();
182  }
183
184  /* ------------------------------------------------------------ client marquee */
185  var firmsEN=[['<span class="cwm"><b>Clifford Chance</b></span>','<span class="cwm"><b>Linklaters</b></span>','<span class="cwm"><b>White &amp; Case</b></span>','<span class="cwm"><b>A&amp;O Shearman</b></span>','<span class="cwm"><b>Freshfields</b></span>','<span class="cwm"><b>Gibson Dunn</b></span>','<span class="cwm"><b>Kirkland &amp; Ellis</b></span>','<span class="cwm"><b>Pinsent Masons</b></span>','<span class="cwm"><b>Eversheds Sutherland</b></span>','<span class="cwm"><b>Al Tamimi &amp; Company</b></span>','<span class="cwm"><b>Hadef &amp; Partners</b></span>','<span class="cwm"><b>Suhail Partners</b></span>','<span class="cwm"><b>Khalifeh &amp; Partners</b></span>','<span class="cwm"><b>Obeidat, Tarawneh &amp; Kurd</b></span>'],['<span class="cwm"><b>UAE Supreme Legislation Committee</b></span>','<span class="cwm"><b>UAE Ministry of Foreign Affairs</b></span>','<span class="cwm"><b>Central Bank of Jordan</b></span>','<span class="cwm"><b>Mubadala</b></span>','<span class="cwm"><b>Abu Dhabi Commercial Bank</b></span>','<span class="cwm"><b>Etihad Rail</b></span>','<span class="cwm"><b>Abu Dhabi Department of Finance</b></span>','<span class="cwm"><b>Executive Affairs Authority</b></span>','<span class="cwm"><b>Abu Dhabi Department of Health</b></span>','<span class="cwm"><b>UNHCR</b></span>','<span class="cwm"><b>EY</b></span>','<span class="cwm"><b>UAE University</b></span>']];
186  var firmsAR=[['<span class="cwm" dir="ltr"><b>Clifford Chance</b></span>','<span class="cwm" dir="ltr"><b>Linklaters</b></span>','<span class="cwm" dir="ltr"><b>White &amp; Case</b></span>','<span class="cwm" dir="ltr"><b>A&amp;O Shearman</b></span>','<span class="cwm" dir="ltr"><b>Freshfields</b></span>','<span class="cwm" dir="ltr"><b>Gibson Dunn</b></span>','<span class="cwm" dir="ltr"><b>Kirkland &amp; Ellis</b></span>','<span class="cwm" dir="ltr"><b>Pinsent Masons</b></span>','<span class="cwm" dir="ltr"><b>Eversheds Sutherland</b></span>','<span class="cwm"><b>التميمي ومشاركوه</b></span>','<span class="cwm"><b>هادف وشركاه</b></span>','<span class="cwm"><b>شركاء سهيل</b></span>','<span class="cwm"><b>خليفة ومشاركوه</b></span>','<span class="cwm"><b>
186عبيدات والطراونة والكرد</b></span>'],['<span class="cwm"><b>اللجنة العليا للتشريعات</b></span>','<span class="cwm"><b>وزارة الخارجية الإماراتية</b></span>','<span class="cwm"><b>البنك المركزي الأردني</b></span>','<span class="cwm"><b>مبادلة</b></span>','<span class="cwm"><b>بنك أبوظبي التجاري</b></span>','<span class="cwm"><b>قطارات الاتحاد</b></span>','<span class="cwm"><b>دائرة المالية – أبوظبي</b></span>','<span class="cwm"><b>جهاز الشؤون التنفيذية</b></span>','<span class="cwm"><b>دائرة الصحة – أبوظبي</b></span>','<span class="cwm"><b>المفوضية السامية للأمم المتحدة لشؤون اللاجئين</b></span>','<span class="cwm" dir="ltr"><b>EY</b></span>','<span class="cwm"><b>جامعة الإمارات</b></span>']];
187  function fillFirms() {
188    var f = LANG === 'ar' ? firmsAR : firmsEN;
189    $('.ctrack').each(function () { var r = f[+$(this).data('row') || 0]; $(this).html(r.concat(r).join('')); });
190  }
191
192  /* ------------------------------------------------- platform mockup (research) */
193  function LOGO() {
194    var el = document.querySelector(LANG === 'ar' ? '.nav .l-light-ar' : '.nav .l-light');
195    return el ? el.getAttribute('src') : '';
196  }
197  function topIcons() {
198    return '<span class="app-top-ic">' + I.folder + '</span><span class="app-top-ic">' + I.clock + '</span><span class="app-top-ic bell"><span class="dot"></span>' + I.bell + '</span><span class="app-top-ic">' + I.mail + '</span><span class="app-top-ic">' + I.sun + '</span><span class="app-top-ic user">' + I.userTop + '</span>';
199  }
200  function sideList(active, expanded) {
201    var s = SIDE[LANG];
202    return '<div class="side-item' + (!expanded ? ' on' : '') + '">' + I.home + '<span>' + s.home + '</span></div>' +
203      '<div class="side-item">' + I.search + '<span>' + s.search + '</span></div>' +
204      '<div class="side-sec">' + s.ai + '</div>' +
205      ASST.map(function (a) {
206        return '<div class="side-item' + (expanded && a.key === active ? ' on' : '') + '" data-go="' + a.key + '">' + a.ic + '<span>' + a.tab[LANG] + '</span></div>';
207      }).join('');
208  }
209  function rhomeInner() {
210    var d = RH[LANG];
211    var blocks = '<span class="rbtool">' + I.crosshair + '</span><span class="rbar-div"></span><span class="rbtool-group"><span class="rbtool on">' + I.key + '</span><span class="rbtool">' + I.sparklePlus + '</span></span><span class="rbar-div"></span>';
212    return '<div class="rhome-top"><div class="rgreet">' + d.hello + '</div><h4>' + d.h + '</h4>' +
213      '<div class="rbar"><span class="rbar-tools">' + blocks + '</span><span class="ph">' + d.ph + '</span><span style="flex:1"></span><span class="rbar-av">' + I.mic + '</span><span class="go"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg></span></div>' +
214      '<div class="rchips">' + d.chips.map(function (c, i) { return '<span' + (i === 0 ? ' class="on"' : '') + '>' + c + '</span>'; }).join('') + '</div></div>' +
215      '<div class="rlower"><div class="rtabs">' + d.tabs.map(function (t, i) { return '<a' + (i === 0 ? ' class="on"' : '') + '>' + t + '</a>'; }).join('') + '</div>' +
216      '<div class="rnews-head"><b>' + (LANG === 'ar' ? 'أحدث الأخبار القانونية' : 'Latest legal news') + '</b></div>' +
217      '<div class="rskel">' + [0, 1, 2, 3].map(function () { return '<div class="skcard"><span class="skb w1"></span><span class="skb w2"></span><span class="skb w3"></span></div>'; }).join('') + '</div></div>';
218  }
219  function fillResearchMock() {
220    var $el = $('#mockResearch');
221    if (!$el.length) return;
222    $el.html('<div class="app"><div class="app-top"><img class="brand" src="' + LOGO() + '" alt=""><span style="flex:1"></span><span class="jpill">' + I.globe + (LANG === 'ar' ? '9 دول عربية' : '9 jurisdictions') + I.chevronDown + '</span><span class="tctl">' + LANG.toUpperCase() + I.chevronDown + '</span>' + topIcons() + '</div>' +
223      '<div class="app-flex"><aside class="app-side">' + sideList('research', false) + '</aside>' +
224      '<div style="flex:1;min-width:0">' + rhomeInner() + '</div></div></div>');
225  }
226
227  /* ---------------------------------------------------- nav over the dark hero */
228  function initNavDark() {
229    var nav = document.getElementById('nav');
230    var hero = document.querySelector('.hero, .ab-hero');
231    if (!nav) return;
232    if (!hero) { nav.classList.remove('on-dark'); return; }
233    function update() {
234      var r = hero.getBoundingClientRect();
235      /* The transparent nav belongs to the resting hero only: as soon as the
236         visitor starts scrolling, the header takes its rgba(255,255,255,.9)
237         background (and with it the dark wordmark and link colours), even while
238         the dark hero is still passing underneath. */
239      var atTop = (window.pageYOffset || document.documentElement.scrollTop || 0) <= 0;
240      nav.classList.toggle('on-dark', atTop && r.bottom > 70);
241    }
242    $(window).on('scroll', update);
243    update();
244  }
245
246  /* ------------------------------------------------------------ sign-in menu */
247  function initSignin() {
248    var $w = $('#signinWrap'), $b = $('#signinBtn');
249    if (!$w.length) return;
250    $b.on('click', function (e) {
251      e.stopPropagation();
252      var open = $w.toggleClass('open').hasClass('open');
253      $b.attr('aria-expanded', open ? 'true' : 'false');
254    });
255    $(document).on('click', function (e) {
256      if (!$w[0].contains(e.target)) { $w.removeClass('open'); $b.attr('aria-expanded', 'false'); }
257    });
258    $(document).on('keydown', function (e) {
259      if (e.key === 'Escape') { $w.removeClass('open'); $b.attr('aria-expanded', 'false'); }
260    });
261  }
262
263  /* --------------------------------------------------------- mobile nav menu */
264  function initHamburger() {
265    var $inner = $('#navInner'), $ham = $('#hamburgerBtn');
266    if (!$inner.length || !$ham.length) return;
267    function close() { $inner.removeClass('menu-open'); $ham.attr('aria-expanded', 'false'); }
268    function open() { $inner.addClass('menu-open'); $ham.attr('aria-expanded', 'true'); }
269    $ham.on('click', function (e) {
270      e.stopPropagation();
271      $inner.hasClass('menu-open') ? close() : open();
272    });
273    $inner.find('.nav-links a').on('click', close);
274    $(document).on('click', function (e) { if (!$inner[0].contains(e.target)) close(); });
275    $(document).on('keydown', function (e) { if (e.key === 'Escape') close(); });
276    $(window).on('resize', function () { if (window.innerWidth > 900) close(); });
277  }
278
279  /* -------------------------------------------------------------- video modal */
280  function initVideoModal() {
281    var $modal = $('#videoModal'), $open = $('#watchVideoBtn');
282    if (!$modal.length || !$open.length) return;
283    function close() { $modal.removeClass('open'); }
284    $('#videoModalClose, #videoModalBackdrop, #videoModalCta').on('click', close);
285    $(document).on('keydown', function (e) { if (e.key === 'Escape') close(); });
286  }
287
288  /* ------------------------------------------------ smooth in-page scrolling */
289  /* jQuery's animated scrollTop fights the global `html{scroll-behavior:smooth}`
290     from the design drop — each frame re-queues a smooth scroll and the page
291     never moves. Use the native scroller, which also honours scroll-margin-top
292     on targets that sit under the sticky nav (e.g. #ctaForm). */
293  function scrollToEl(el) {
294    if (!el) return;
295    el.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' });
296  }
297  function initAnchors() {
298    $(document).on('click', 'a[href^="#"]', function (e) {
299      var id = $(this).attr('href').slice(1);
300      if (!id) return;
301      var el = document.getElementById(id);
302      if (!el) return;
303      e.preventDefault();
304      scrollToEl(el);
305    });
306    /* hero suite cards jump to the assistants section */
307    $('.hcard[data-assistant]').on('click', function (e) {
308      e.preventDefault();
309      scrollToEl(document.getElementById('assistants'));
310    });
311  }
312
313  /* --------------------------------------------- about page: canvas starfield */
314  function initAboutStarfield() {
315    var cv = document.getElementById('starfield');
316    if (!cv) return;
317    var ctx = cv.getContext('2d'), stars = [], w, h;
318    function build() {
319      stars = [];
320      var n = Math.min(120, Math.floor(w * h / 22000));
321      for (var i = 0; i < n; i++) stars.push({
322        x: Math.random() * w, y: Math.random() * h,
323        r: (Math.random() * 1.1 + 0.25) * devicePixelRatio,
324        a: Math.random() * 6.28, tw: Math.random() * 0.012 + 0.003,
325        vy: (Math.random() * 0.03 + 0.01) * devicePixelRatio
326      });
327    }
328    function size() { w = cv.width = cv.offsetWidth * devicePixelRatio; h = cv.height = cv.offsetHeight * devicePixelRatio; build(); }
329    function draw() {
330      ctx.clearRect(0, 0, w, h);
331      for (var i = 0; i < stars.length; i++) {
332        var s = stars[i];
333        s.a += s.tw;
334        var al = 0.22 + Math.abs(Math.sin(s.a)) * 0.4;
335        if (!reduce) { s.y += s.vy; if (s.y > h) { s.y = 0; s.x = Math.random() * w; } }
336        ctx.beginPath();
337        ctx.fillStyle = 'rgba(180,214,255,' + al + ')';
338        ctx.arc(s.x, s.y, s.r, 0, 6.28);
339        ctx.fill();
340      }
341      requestAnimationFrame(draw);
342    }
343    size();
344    $(window).on('resize', size);
345    draw();
346  }
347
348  /* ---------------------------------------------------------------- forms
349     Both forms post normally to the server endpoint in their action attribute
350     (wire this to your PHP/back-end handler). jQuery only guards double submits. */
351  function initForms() {
352    $('form.qform').not('.crmWebToEntityForm').on('submit', function () {
353      var $b = $(this).find('button[type=submit]');
354      $b.prop('disabled', true).text(LANG === 'ar' ? 'جارٍ الإرسال…' : 'Sending…');
355    });
356  }
357
358
359  /* -------------------------------------------------------------- page loader
360     Fades the white loading screen out once the page is ready; the timeout is a
361     failsafe so a slow font or image can never leave it on screen. */
362  function initLoader() {
363    var $l = $('#pageLoader');
364    if (!$l.length) return;
365    var done = false;
366    function hide() {
367      if (done) return;
368      done = true;
369      $l.addClass('is-done');
370      setTimeout(function () { $l.remove(); }, 450);
371    }
372    $(window).on('load', function () { setTimeout(hide, 150); });
373    setTimeout(hide, 4000);
374    if (document.readyState === 'complete') setTimeout(hide, 150);
375  }
376
377  /* --------------------------------------------------------- enquiry routing
378     The contact form serves three arrivals. A partner or enterprise enquiry
379     cannot be assessed without the organisation details, so on those the form
380     says what it is for, shows the message field, and requires organisation,
381     size and country. Everything else keeps the short form. */
382  function initEnquiry() {
383    var $form = $('#webform5021687000073835197');
384    if (!$form.length) return;
385    var kind = new URLSearchParams(location.search).get('enquiry');
386    if (kind !== 'partner' && kind !== 'enterprise') return;
387    var ar = LANG === 'ar';
388    var T = {
389      partner: {
390        note: ar ? 'طلب شراكة — يرجى تعبئة بيانات المؤسسة والسوق الذي تغطيه.'
391                 : 'Partnership enquiry — please complete your organisation details and the market you cover.',
392        msg: ar ? 'نشاطكم الحالي وخطتكم للتمثيل *' : 'Your current activity and how you would represent Qistas *',
393        country: ar ? 'الدولة / السوق *' : 'Country / market *'
394      },
395      enterprise: {
396        note: ar ? 'استفسار مؤسسي — أخبرنا عن مؤسستكم وحجمها واحتياجكم، وسيتواصل معكم فريق المؤسسات.'
397                 : 'Enterprise enquiry — tell us about your organisation, its size and what you need, and our enterprise team will get in touch.',
398        msg: ar ? 'احتياجكم: التشغيل، والتكامل، وعدد المستخدمين، والجدول الزمني *'
399                : 'What you need: deployment, integrations, number of users, timeline *',
400        country: ar ? 'الدولة *' : 'Country *'
401      }
402    }[kind];
403    var COMPANY = ar ? 'اسم المؤسسة *' : 'Organisation name *';
404    var REQ = ar ? 'يرجى تعبئة بيانات المؤسسة: الاسم، والنوع، والحجم، والدولة، ووصف احتياجكم.'
405                 : 'Please complete your organisation details: name, type, size, country, and a description of what you need.';
406
407    $form.addClass('is-detailed').prepend('<p class="qf-note">' + T.note + '</p>');
408    $('#Company').attr({ placeholder: COMPANY, 'aria-required': 'true' });
409    $('#Country').attr({ placeholder: T.country, 'aria-required': 'true' });
410    $('#LEADCF1').attr({ placeholder: T.msg, 'aria-required': 'true', rows: 6 });
411    $('#LEADCF16, #LEADCF17').attr('aria-required', 'true');
412    if (kind === 'enterprise') $('#LEADCF18').val('Full Qistas Suite');
413
414    $form.on('submit', function (e) {
415      var missing = !$('#Company').val().trim() || !$('#Country').val().trim() || !$('#LEADCF1').val().trim() ||
416                    $('#LEADCF16').val() === '-None-' || $('#LEADCF17').val() === '-None-';
417      if (missing) { e.preventDefault(); e.stopImmediatePropagation(); alert(REQ); return false; }
418    });
419  }
420
421  /* ----------------------------------------------------------- policy dialog
422     "Read the privacy policy" opens the document over the page instead of
423     navigating away, so the visitor does not lose their place in the section.
424     The policy page itself is fetched once and cached; if the fetch cannot run
425     (opened from the filesystem, for instance) the link behaves normally. */
426  function initPolicyModal() {
427    /* Laravel serves the page at /{locale}/privacy-policy — no .html suffix —
428       so the policy URL is read off the link itself rather than assumed. */
429    var $links = $('.dep-note a[href$="privacy-policy.html"], .dep-note a[href$="/privacy-policy"]');
430    if (!$links.length || !window.fetch) return;
431    var policyUrl = $links.first().attr('href');
432    var ar = LANG === 'ar';
433    var $m = $(
434      '<div class="doc-modal" id="policyModal" role="dialog" aria-modal="true" aria-label="' +
435      (ar ? 'سياسة الخصوصية' : 'Privacy Policy') + '">' +
436        '<div class="doc-modal-back"></div>' +
437        '<div class="doc-modal-panel">' +
438          '<div class="doc-modal-head">' +
439            '<b>' + (ar ? 'سياسة الخصوصية' : 'Privacy Policy') + '</b>' +
440            '<a class="doc-modal-full" href="' + policyUrl + '">' + (ar ? 'فتح الصفحة' : 'Open the page') + '</a>' +
441            '<button type="button" class="doc-modal-x" aria-label="' + (ar ? 'إغلاق' : 'Close') + '">' +
442              '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M6 6l12 12M18 6 6 18"></path></svg>
442' +
443            '</button>' +
444          '</div>' +
445          '<div class="doc-modal-body legal"><p class="doc-modal-load">' + (ar ? 'جارٍ التحميل…' : 'Loading…') + '</p></div>' +
446        '</div>' +
447      '</div>');
448    $('body').append($m);
449
450    var loaded = false, lastFocus = null;
451    function close() {
452      $m.removeClass('on');
453      $('html').css('overflow', '');
454      if (lastFocus) lastFocus.focus();
455    }
456    function load() {
457      if (loaded) return;
458      loaded = true;
459      fetch(policyUrl)
460        .then(function (r) { return r.text(); })
461        .then(function (html) {
462          var doc = new DOMParser().parseFromString(html, 'text/html');
463          var body = doc.querySelector('main.legal .wrap');
464          if (!body) throw new Error('policy body not found');
465          $m.find('.doc-modal-body').html(body.innerHTML);
466        })
467        .catch(function () { loaded = false; location.href = policyUrl; });
468    }
469
470    $links.on('click', function (e) {
471      e.preventDefault();
472      lastFocus = this;
473      load();
474      $m.addClass('on');
475      $('html').css('overflow', 'hidden');
476      $m.find('.doc-modal-x').trigger('focus');
477    });
478    $m.on('click', '.doc-modal-back, .doc-modal-x', close);
479    $(document).on('keydown', function (e) { if (e.key === 'Escape') close(); });
480  }
481
482  /* ------------------------------------------------------------- form dialog
483     Any link marked data-form-modal opens the contact form over the page rather
484     than navigating to it. The form is fetched from contact.html once, so there
485     is a single copy of it to maintain; if the fetch cannot run the link works
486     as an ordinary link. */
487  function initFormModal() {
488    var $links = $('a[data-form-modal]');
489    if (!$links.length || !window.fetch) return;
490    var ar = LANG === 'ar';
491    var $m = $(
492      '<div class="doc-modal form-modal" id="formModal" role="dialog" aria-modal="true" aria-label="' +
493      (ar ? 'تواصل معنا' : 'Contact us') + '">' +
494        '<div class="doc-modal-back"></div>' +
495        '<div class="doc-modal-panel">' +
496          '<div class="doc-modal-head">' +
497            '<b>' + (ar ? 'تواصل معنا' : 'Contact us') + '</b>' +
498            '<button type="button" class="doc-modal-x" aria-label="' + (ar ? 'إغلاق' : 'Close') + '">' +
499              '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M6 6l12 12M18 6 6 18"></path></svg>' +
500            '</button>' +
501          '</div>' +
502          '<div class="doc-modal-body"><p class="doc-modal-load">' + (ar ? 'جارٍ التحميل…' : 'Loading…') + '</p></div>' +
503        '</div>' +
504      '</div>');
505    $('body').append($m);
506
507    var loaded = false, lastFocus = null;
508    function close() {
509      $m.removeClass('on');
510      $('html').css('overflow', '');
511      if (lastFocus) lastFocus.focus();
512    }
513    function load(href) {
514      if (loaded) return;
515      loaded = true;
516      fetch(href.split('#')[0])
517        .then(function (r) { return r.text(); })
518        .then(function (html) {
519          var doc = new DOMParser().parseFromString(html, 'text/html');
520          var form = doc.querySelector('form.qform.crmWebToEntityForm');
521          if (!form) throw new Error('form not found');
522          $m.find('.doc-modal-body').empty().append(form);
523        })
524        .catch(function () { loaded = false; location.href = href; });
525    }
526
527    $links.on('click', function (e) {
528      e.preventDefault();
529      lastFocus = this;
530      load($(this).attr('href'));
531      $m.addClass('on');
532      $('html').css('overflow', 'hidden');
533      $m.find('.doc-modal-x').trigger('focus');
534    });
535    $m.on('click', '.doc-modal-back, .doc-modal-x', close);
536    $(document).on('keydown', function (e) { if (e.key === 'Escape') close(); });
537  }
538
539  $(function () {
540    initLoader();
541    initFormModal();
542    initPolicyModal();
543    initEnquiry();
544    initStars();
545    armReveals();
546    armTrules();
547    revealVisible();
548    $(window).on('scroll', revealVisible);
549    $(window).on('load', revealVisible);
550    setTimeout(revealVisible, 600);
551    initCounters();
552    startTyper();
553    fillFirms();
554    fillResearchMock();
555    initNavDark();
556    initSignin();
557    initHamburger();
558    initVideoModal();
559    initAnchors();
560    initAboutStarfield();
561    initForms();
562  });
563})(jQuery);

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.