PageSourceSearch

https://catking.in/new-frontend/js/catking-home.js

js catking.in collected 2026-10-01 15:55:13 UTC 48,965 bytes, 1,195 lines download raw bytes

1(function () {
2  "use strict";
3
4  // ── Reveal-on-scroll ──
5  const obs = new IntersectionObserver((entries) => {
6    entries.forEach(e => {
7      if (e.isIntersecting) {
8        e.target.classList.add('in');
9        // Once revealed, we don't need to observe it anymore
10        obs.unobserve(e.target);
11      }
12    });
13  }, { threshold: 0.15 });
14
15  function initReveal() {
16    document.querySelectorAll('.reveal').forEach(el => {
17      // If already in view (e.g. on page reload), add .in immediately
18      const rect = el.getBoundingClientRect();
19      if (rect.top < window.innerHeight && rect.bottom > 0) {
20        el.classList.add('in');
21      } else {
22        obs.observe(el);
23      }
24    });
25  }
26
27  // Run on load and after a short delay to catch late-renders
28  initReveal();
29  setTimeout(initReveal, 400);
30
31  // (Hero carousel removed - replaced by video player)
32
33  // Count-up stat animation
34  ; (function () {
35    function formatNumber(value, format, suffix) {
36      let str;
37      if (format === 'decimal') {
38        str = value.toFixed(1);
39      } else if (format === 'comma') {
40        str = Math.round(value).toLocaleString('en-IN');
41      } else if (format === 'percent') {
42        str = Math.round(value) + '%';
43      } else {
44        str = Math.round(value).toString();
45      }
46      return suffix ? str + suffix : str;
47    }
48
49    function setFinalValue(el) {
50      const target = parseFloat(el.dataset.countTo);
51      const format = el.dataset.countFormat || 'plain';
52      const suffix = el.dataset.countSuffix || '';
53      el.textContent = formatNumber(target, format, suffix);
54    }
55
56    function animateCount(el) {
57      if (el.dataset.countDone === '1') return;
58      el.dataset.countDone = '1';
59      const target = parseFloat(el.dataset.countTo);
60      const format = el.dataset.countFormat || 'plain';
61      const suffix = el.dataset.countSuffix || '';
62      const duration = 2600;
63      const start = performance.now();
64
65      function tick(now) {
66        const progress = Math.min((now - start) / duration, 1);
67        const eased = 1 - Math.pow(1 - progress, 3);
68        el.textContent = formatNumber(target * eased, format, suffix);
69        if (progress < 1) {
70          requestAnimationFrame(tick);
71        } else {
72          setFinalValue(el);
73        }
74      }
75
76      requestAnimationFrame(tick);
77    }
78
79    const counters = document.querySelectorAll('[data-count-to]');
80    if (!counters.length) return;
81
82    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
83      counters.forEach(setFinalValue);
84      return;
85    }
86
87    if (!('IntersectionObserver' in window)) {
88      counters.forEach(animateCount);
89      return;
90    }
91
92    const counterObserver = new IntersectionObserver((entries) => {
93      entries.forEach(entry => {
94        if (entry.isIntersecting) {
95          animateCount(entry.target);
96          counterObserver.unobserve(entry.target);
97        }
98      });
99    }, { threshold: 0.4 });
100
101    counters.forEach(el => counterObserver.observe(el));
102  })();
103
104
105  // ── FAQ toggle ──
106  window.toggleFaq = function(btn) {
107    const item = btn.parentElement;
108    const wasOpen = item.classList.contains('open');
109    document.querySelectorAll('.faq-item').forEach(i => i.classList.remove('open'));
110    if (!wasOpen) item.classList.add('open');
111  };
112
113  // ── Seats bar fill ──
114  setTimeout(() => {
115    const bar = document.getElementById('seatsFill');
116    if (bar) bar.style.width = '46%'; // 37/80 ≈ 46%
117  }, 600);
118
119  // ── Sticky CTA show on scroll ──
120  // Sticky CTA disabled for now.
121  let stickyClosed = true;
122  window.addEventListener('scroll', () => {
123    const sc = document.getElementById('stickyCta');
124    if (!sc || stickyClosed) return;
125    if (window.scrollY > 800 && window.scrollY < (document.body.scrollHeight - 1200)) {
126      sc.classList.add('show');
127    } else {
128      sc.classList.remove('show');
129    }
130  });
131
132  // ── Live enrollment toast ──
133  const toastData = [
134    { init: 'PR', name: 'Priya R. · Delhi', msg: 'just enrolled in CAT 2026 Complete', time: '2 min ago' },
135    { init: 'AK', name: 'Arjun K. · Pune', msg: 'started the Free Diagnostic Mock', time: '5 min ago' },
136    { init: 'MS', name: 'Meera S. · Bangalore', msg: 'registered for Sunday Strategy Webinar', time: '7 min ago' },
137    { init: 'RV', name: 'Rohit V. · Mumbai', msg: 'reserved a seat in May 4 batch', time: '11 min ago' },
138    { init: 'DP', name: 'Divya P. · Hyderabad', msg: 'downloaded the Free 6-Month Planner', time: '13 min ago' },
139    { init: 'SK', name: 'Saurabh K. · Chennai', msg: 'booked a free counselling call', time: '16 min ago' },
140  ];
141  let tIdx = 0;
142  function showToast() {
143    const t = toastData[tIdx % toastData.length];
144    const el = document.getElementById('liveToast');
145    if (!el) return;
146    const av = document.getElementById('toastAvatar');
147    const nm = document.getElementById('toastName');
148    const ms = document.getElementById('toastMsg');
149    const tm = document.getElementById('toastTime');
150    if (av) av.textContent = t.init;
151    if (nm) nm.textContent = t.name;
152    if (ms) ms.textContent = t.msg;
153    if (tm) tm.textContent = t.time;
154    el.style.display = 'flex';
155    el.style.animation = 'none'; el.offsetHeight; el.style.animation = 'slideUp 0.4s ease';
156    tIdx++;
157    setTimeout(() => { el.style.opacity = '0'; setTimeout(() => { el.style.display = 'none'; el.style.opacity = '1'; }, 400); }, 5500);
158  }
159  // Live enrollment toast disabled for now.
160  // setTimeout(() => { showToast(); setInterval(showToast, 16000); }, 6000);
161
162  // ─── PDF DOWNLOAD MODAL ───
163  const PDF_LIST = [
164    { name: 'Formula Book', sub: 'Quant + DI shortcuts · 60 pages' },
165    { name: 'Must Do CAT Questions', sub: '500 high-frequency CAT questions' },
166    { name: 'CAT Handbook', sub: 'Complete syllabus + strategy guide' },
167    { name: 'XAT Handbook', sub: 'Decision-making + GK prep guide' },
168    { name: '50 Must Do LR Caselets', sub: 'Logical reasoning practice set' },
169    { name: 'Top Bschool Interview Dockets', sub: 'IIM A/B/C/L interview transcripts' },
170    { name: 'CMAT Handbook', sub: 'CMAT pattern + practice set' },
171    { name: 'CET Handbook', sub: 'MAH-CET 2026 complete guide' },
172    { name: 'NMAT Handbook', sub: 'NMIMS admission strategy' },
173    { name: 'SNAP Handbook', sub: 'Symbiosis exam pattern + tips' },
174    { name: 'Finance Dossier', sub: 'B-school finance terms decoded' }
175  ];
176
177  let selectedPdf = null;
178  let signMode = 'signin'; // 'signin' or 'signup'
179
180  function pdfInitial(name) {
181    return name.split(' ').filter(w => w.length > 1).slice(0, 2).map(w => w[0]).join('').toUpperCase();
182  }
183
184  function renderPdfGrid() {
185    const grid = document.getElementById('pdfGrid');
186    if (!grid) return;
187    grid.innerHTML = PDF_LIST.map((p, i) => `
188      <button class="pdf-item" type="button" onclick="selectPdf(${i})">
189        <span class="pdf-icon">PDF</span>
190        <span class="pdf-info">
191          <span class="pdf-name">${p.name}</span>
192          <span class="pdf-sub">${p.sub}</span>
193        </span>
194        <span class="pdf-item-arrow" aria-hidden="true">›</span>
195      </button>
196    `).join('');
197  }
198  renderPdfGrid();
199
200  // ── Scorecard Modal ──
201  const STUDENT_SCORECARDS = [
202    {
203      initials: 'PC', name: 'Protham Saha Chowdhury', exam: 'CAT 2024-25',
204      pct: '99.91',
205      sections: [['VARC', '—', '98.17'], ['DILR', '—', '100.00'], ['Quant', '—', '95.31']],
206      overall: ['—', '99.91'],
207      quote: "CATKing's mocks were calibrated perfectly to actual exam difficulty — the wave-based launches kept me consistently challenged and improving week after week.",
208      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.91%ile'
209    },
210    {
211      initials: 'NA', name: 'Naman', exam: 'CAT 2024-25',
212      pct: '98.98',
213      sections: [['VARC', '—', '92.59'], ['DILR', '—', '96.13'], ['Quant', '—', '99.70']],
214      overall: ['—', '98.98'],
215      quote: "Sumit Sir's VARC modules cracked the toughest section for me. The doubt-clearing sessions and recorded videos made all the difference in my prep.",
216      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.98%ile'
217    },
218    {
219      initials: 'AS', name: 'Aashna Suneja', exam: 'CAT 2024-25',
220      pct: '98.48',
221      sections: [['VARC', '—', '92.59'], ['DILR', '—', '98.78'], ['Quant', '—', '96.91']],
222      overall: ['—', '98.48'],
223      quote: "The level of mentorship at CATKing is unmatched. From doubt-clearing to interview prep, every stage was handled with precision and personal attention.",
224      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.48%ile'
225    },
226    {
227      initials: 'RK', name: 'Rohan Khanna', exam: 'CAT 2024-25',
228      pct: '99.37',
229      sections: [['VARC', '—', '99.85'], ['DILR', '—', '95.87'], ['Quant', '—', '93.36']],
230      overall: ['—', '99.37'],
231      quote: "CATKing's structured curriculum and analytics dashboard helped me identify weak areas early. The mocks felt exactly like the real CAT — no surprises on D-day.",
232      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.37%ile'
233    },
234    {
235      initials: 'AS', name: 'Anssh Sharma', exam: 'CAT 2024-25',
236      pct: '99.53',
237      sections: [['VARC', '—', '93.92'], ['DILR', '—', '99.89'], ['Quant', '—', '97.64']],
238      overall: ['—', '99.53'],
239      quote: "Daily live classes from IIM-A/B/C alumni faculty plus the self-study library gave me everything I needed. The 1-on-1 mentor calls were game-changers.",
240      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.53%ile'
241    },
242    {
243      initials: 'DK', name: 'Divyansh Kumar', exam: 'CAT 2024-25',
244      pct: '99.16',
245      sections: [['VARC', '—', '95.04'], ['DILR', '—', '99.06'], ['Quant', '—', '94.45']],
246      overall: ['—', '99.16'],
247      quote: "The 130+ LOD1 & LOD2 practice e-books built my fundamentals brick by brick. By mock 8, I was scoring 95+ consistently.",
248      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.16%ile'
249    },
250    {
251      initials: 'PB', name: 'Prakhar Baheti', exam: 'CAT 2024-25',
252      pct: '99.02',
253      sections: [['VARC', '—', '93.92'], ['DILR', '—', '99.86'], ['Quant', '—', '92.22']],
254      overall: ['—', '99.02'],
255      quote: "What sets CATKing apart is the small-batch attention. Daily targets and weekend strategy sessions kept me accountable through the entire 6-month grind.",
256      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.02%ile'
257    },
258    {
259      initials: 'DA', name: 'Divyam Agarwal', exam: 'CAT 2024-25',
260      pct: '99.00',
261      sections: [['VARC', '—', '91.64'], ['DILR', '—', '99.70'], ['Quant', '—', '96.91']],
262      overall: ['—', '99.00'],
263      quote: "From struggling with %tiles to walking into my dream IIM — CATKing's faculty and mock series made the journey not just possible but enjoyable.",
264      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.00%ile'
265    },
266    {
267      initials: 'AS', name: 'Amrit Singh', exam: 'CAT 2024-25',
268      pct: '98.92',
269      sections: [['VARC', '—', '97.66'], ['DILR', '—', '91.74'], ['Quant', '—', '95.12']],
270      overall: ['—', '98.92'],
271      quote: "The MICA-alumni mock interviews and SOP reviews polished my profile in 
271ways I didn't think were possible. Truly end-to-end prep.",
272      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.92%ile'
273    },
274    {
275      initials: 'SS', name: 'Shivam Singh', exam: 'CAT 2024-25',
276      pct: '98.72',
277      sections: [['VARC', '—', '97.66'], ['DILR', '—', '91.74'], ['Quant', '—', '99.77']],
278      overall: ['—', '98.72'],
279      quote: "Speed-focused strategy is what I needed for CAT, and CATKing delivered. The sectional drills helped me cross 99 in DILR after months of being stuck at 90.",
280      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.72%ile'
281    },
282    {
283      initials: 'PK', name: 'Parushi Singh Kunwar', exam: 'CAT 2024-25',
284      pct: '98.57',
285      sections: [['VARC', '—', '97.37'], ['DILR', '—', '99.34'], ['Quant', '—', '82.87']],
286      overall: ['—', '98.57'],
287      quote: "CATKing's mocks were calibrated perfectly to actual exam difficulty — the wave-based launches kept me consistently challenged and improving week after week.",
288      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.57%ile'
289    },
290    {
291      initials: 'AK', name: 'Anish Kumar', exam: 'CAT 2024-25',
292      pct: '98.42',
293      sections: [['VARC', '—', '89.91'], ['DILR', '—', '97.98'], ['Quant', '—', '98.93']],
294      overall: ['—', '98.42'],
295      quote: "Sumit Sir's VARC modules cracked the toughest section for me. The doubt-clearing sessions and recorded videos made all the difference in my prep.",
296      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.42%ile'
297    },
298    {
299      initials: 'PK', name: 'Pranav Kumar', exam: 'CAT 2024-25',
300      pct: '98.40',
301      sections: [['VARC', '—', '89.91'], ['DILR', '—', '99.57'], ['Quant', '—', '95.04']],
302      overall: ['—', '98.40'],
303      quote: "The level of mentorship at CATKing is unmatched. From doubt-clearing to interview prep, every stage was handled with precision and personal attention.",
304      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.40%ile'
305    },
306    {
307      initials: 'NP', name: 'Nikhil Patil', exam: 'CAT 2024-25',
308      pct: '98.32',
309      sections: [['VARC', '—', '87.85'], ['DILR', '—', '97.96'], ['Quant', '—', '99.12']],
310      overall: ['—', '98.32'],
311      quote: "CATKing's structured curriculum and analytics dashboard helped me identify weak areas early. The mocks felt exactly like the real CAT — no surprises on D-day.",
312      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.32%ile'
313    },
314    {
315      initials: 'PS', name: 'Pulkit Saini', exam: 'CAT 2024-25',
316      pct: '98.00',
317      sections: [['VARC', '—', '92.86'], ['DILR', '—', '99.06'], ['Quant', '—', '93.36']],
318      overall: ['—', '98.00'],
319      quote: "Daily live classes from IIM-A/B/C alumni faculty plus the self-study library gave me everything I needed. The 1-on-1 mentor calls were game-changers.",
320      converts: 'Top IIMs & B-Schools', addl: 'CAT 98.00%ile'
321    },
322    {
323      initials: 'KP', name: 'Karthik Puppala', exam: 'CAT 2024-25',
324      pct: '97.97',
325      sections: [['VARC', '—', '85.47'], ['DILR', '—', '98.43'], ['Quant', '—', '98.66']],
326      overall: ['—', '97.97'],
327      quote: "The 130+ LOD1 & LOD2 practice e-books built my fundamentals brick by brick. By mock 8, I was scoring 95+ consistently.",
328      converts: 'Top IIMs & B-Schools', addl: 'CAT 97.97%ile'
329    },
330    {
331      initials: 'DK', name: 'Dheeraj Kumar', exam: 'CAT 2024-25',
332      pct: '96.80',
333      sections: [['VARC', '—', '95.47'], ['DILR', '—', '89.96'], ['Quant', '—', '95.75']],
334      overall: ['—', '96.80'],
335      quote: "What sets CATKing apart is the small-batch attention. Daily targets and weekend strategy sessions kept me accountable through the entire 6-month grind.",
336      converts: 'Top IIMs & B-Schools', addl: 'CAT 96.80%ile'
337    },
338    {
339      initials: 'HB', name: 'Himanshu Bahekar', exam: 'CAT 2024-25',
340      pct: '96.50',
341      sections: [['VARC', '—', '94.16'], ['DILR', '—', '92.87'], ['Quant', '—', '94.70']],
342      overall: ['—', '96.50'],
343      quote: "From struggling with %tiles to walking into my dream IIM — CATKing's faculty and mock series made the journey not just possible but enjoyable.",
344      converts: 'Top IIMs & B-Schools', addl: 'CAT 96.50%ile'
345    },
346    {
347      initials: 'ST', name: 'Sahil Taneja', exam: 'CAT 2024-25',
348      pct: '96.26',
349      sections: [['VARC', '—', '93.13'], ['DILR', '—', '86.81'], ['Quant', '—', '97.64']],
350      overall: ['—', '96.26'],
351      quote: "The MICA-alumni mock interviews and SOP reviews polished my profile in 
351ways I didn't think were possible. Truly end-to-end prep.",
352      converts: 'Top IIMs & B-Schools', addl: 'CAT 96.26%ile'
353    },
354    {
355      initials: 'PS', name: 'Prakhar Kumar Srivastava', exam: 'CAT 2024-25',
356      pct: '95.78',
357      sections: [['VARC', '—', '95.09'], ['DILR', '—', '97.27'], ['Quant', '—', '60.78']],
358      overall: ['—', '95.78'],
359      quote: "Speed-focused strategy is what I needed for CAT, and CATKing delivered. The sectional drills helped me cross 99 in DILR after months of being stuck at 90.",
360      converts: 'Top IIMs & B-Schools', addl: 'CAT 95.78%ile'
361    },
362    {
363      initials: 'SU', name: 'Sunaina', exam: 'CAT 2024-25',
364      pct: '95.70',
365      sections: [['VARC', '—', '85.99'], ['DILR', '—', '97.49'], ['Quant', '—', '93.63']],
366      overall: ['—', '95.70'],
367      quote: "CATKing's mocks were calibrated perfectly to actual exam difficulty — the wave-based launches kept me consistently challenged and improving week after week.",
368      converts: 'Top IIMs & B-Schools', addl: 'CAT 95.70%ile'
369    },
370    {
371      initials: 'UR', name: 'Udit Pragadeesh R', exam: 'CAT 2024-25',
372      pct: '95.54',
373      sections: [['VARC', '—', '92.59'], ['DILR', '—', '96.13'], ['Quant', '—', '87.97']],
374      overall: ['—', '95.54'],
375      quote: "Sumit Sir's VARC modules cracked the toughest section for me. The doubt-clearing sessions and recorded videos made all the difference in my prep.",
376      converts: 'Top IIMs & B-Schools', addl: 'CAT 95.54%ile'
377    },
378    {
379      initials: 'UA', name: 'Ujjawal Awasthi', exam: 'CAT 2024-25',
380      pct: '95.50',
381      sections: [['VARC', '—', '86.53'], ['DILR', '—', '45.38'], ['Quant', '—', '96.11']],
382      overall: ['—', '95.50'],
383      quote: "The level of mentorship at CATKing is unmatched. From doubt-clearing to interview prep, every stage was handled with precision and personal attention.",
384      converts: 'Top IIMs & B-Schools', addl: 'CAT 95.50%ile'
385    },
386    {
387      initials: 'DS', name: 'Divyanshu Kumar Singh', exam: 'CAT 2024-25',
388      pct: '94.86',
389      sections: [['VARC', '—', '95.47'], ['DILR', '—', '90.91'], ['Quant', '—', '82.87']],
390      overall: ['—', '94.86'],
391      quote: "CATKing's structured curriculum and analytics dashboard helped me identify weak areas early. The mocks felt exactly like the real CAT — no surprises on D-day.",
392      converts: 'Top IIMs & B-Schools', addl: 'CAT 94.86%ile'
393    },
394    {
395      initials: 'VS', name: 'Vaibhav Raj Sahni', exam: 'CAT 2024-25',
396      pct: '99.52',
397      sections: [['VARC', '—', '95.09'], ['DILR', '—', '99.21'], ['Quant', '—', '99.24']],
398      overall: ['—', '99.52'],
399      quote: "Daily live classes from IIM-A/B/C alumni faculty plus the self-study library gave me everything I needed. The 1-on-1 mentor calls were game-changers.",
400      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.52%ile'
401    },
402    {
403      initials: 'LA', name: 'Lokesh Agarwal', exam: 'CAT 2024-25',
404      pct: '99.44',
405      sections: [['VARC', '—', '99.85'], ['DILR', '—', '99.13'], ['Quant', '—', '95.75']],
406      overall: ['—', '99.44'],
407      quote: "The 130+ LOD1 & LOD2 practice e-books built my fundamentals brick by brick. By mock 8, I was scoring 95+ consistently.",
408      converts: 'Top IIMs & B-Schools', addl: 'CAT 99.44%ile'
409    },
410    {
411      initials: 'UB', name: 'Uttarak Bansal', exam: 'CAT 2024-25',
412      pct: '93.04',
413      sections: [['VARC', '—', '90.64'], ['DILR', '—', '85.68'], ['Quant', '—', '92.86']],
414      overall: ['—', '93.04'],
415      quote: "What sets CATKing apart is the small-batch attention. Daily targets and weekend strategy sessions kept me accountable through the entire 6-month grind.",
416      converts: 'Top IIMs & B-Schools', addl: 'CAT 93.04%ile'
417    },
418  ];
419
420  let apiScorecards = [];
421
422  function escapeHtml(value) {
423    return String(value || '')
424      .replace(/&/g, '&amp;')
425      .replace(/</g, '&lt;')
426      .replace(/>/g, '&gt;')
427      .replace(/"/g, '&quot;')
428      .replace(/'/g, '&#039;');
429  }
430
431  function safeImageUrl(value) {
432    const url = String(value || '').trim().replace(/\\/g, '/');
433    if (!url) return '';
434    if (/^(https?:)?\/\//i.test(url) || url.startsWith('/') || /^data:image\//i.test(url)) return url;
435    return '';
436  }
437
438  function stripTags(value) {
439    const holder = document.createElement('div');
440    holder.innerHTML = String(value || '');
441    return holder.textContent || holder.innerText || '';
442  }
443
444  function initialsFromName(name) {
445    const parts = String(name || 'Student').trim().split(/\s+/).filter(
445Boolean);
446    return ((parts[0] && parts[0][0]) || 'S') + ((parts[1] && parts[1][0]) || '');
447  }
448
449  function normalizePercentile(value) {
450    return String(value || '').replace(/%?ile/ig, '').replace(/%/g, '').trim();
451  }
452
453  function getApiAvatar(item) {
454    return safeImageUrl(item.user_profile_link || (item.user && item.user.avatar_url));
455  }
456
457  function toApiScorecard(item) {
458    const name = item.user_name || (item.user && item.user.name) || 'Student';
459    const percentile = normalizePercentile(item.user_percentile || item.percentile || item.score);
460    return {
461      initials: initialsFromName(name).toUpperCase(),
462      name,
463      label: item.user_label || 'CATKing CAT Topper',
464      exam: item.exam_label || item.exam || 'CAT 2025',
465      pct: percentile || '99+',
466      quote: stripTags(item.scorecard_description || item.description || ''),
467      avatar: getApiAvatar(item),
468      cardImage: safeImageUrl(item.scorecard_card_image || item.card_image),
469      fullImage: safeImageUrl(item.scorecard_full_image || item.full_image || item.scorecard_card_image || item.card_image)
470    };
471  }
472
473  function renderApiStoryCard(item, index, isDuplicate) {
474    const s = toApiScorecard(item);
475    const avatar = s.avatar
476      ? `<img src="${escapeHtml(s.avatar)}" alt="${escapeHtml(s.name)}" class="story-avatar-img" loading="lazy" onerror="this.remove()">`
477      : escapeHtml(s.initials);
478    const preview = s.cardImage
479      ? `<img src="${escapeHtml(s.cardImage)}" alt="${escapeHtml(s.name)} scorecard" class="story-scorecard-img" loading="lazy">`
480      : `<div class="story-sections-header"><div>VARC</div><div>DILR</div><div>QA</div><div>Total</div></div><div class="story-sec-grid values"><div><span>-</span><span>-</span></div><div><span>-</span><span>-</span></div><div><span>-</span><span>-</span></div><div><span>-</span><span>${escapeHtml(s.pct)}</span></div></div>`;
481    const quote = s.quote || 'Verified CATKing scorecard and success story.';
482
483    return `
484      <article class="story-card"${isDuplicate ? ' aria-hidden="true"' : ''}>
485        <div class="story-head">
486          <div class="story-avatar">${avatar}</div>
487          <div class="story-head-info">
488            <div class="story-name">${escapeHtml(s.name)}</div>
489            <div class="story-sub">${escapeHtml(s.label)}</div>
490          </div>
491        </div>
492        <div class="story-score-banner">
493          <div class="story-score-pct">${escapeHtml(s.pct)}<sup>%ile</sup></div>
494          <div class="story-score-lbl">CAT Percentile Score</div>
495        </div>
496        <div class="story-sections api-scorecard-preview">${preview}</div>
497        <div class="story-quote">${escapeHtml(quote)}</div>
498        <button class="story-btn" type="button" onclick="openScorecard(${index})">View scorecard</button>
499      </article>`;
500  }
501
502  function setScorecardImageMode(showImage) {
503    const wrap = document.getElementById('scoreImageWrap');
504    const table = document.querySelector('#scoreSections')?.closest('table');
505    const quote = document.getElementById('scoreQuote');
506    const meta = document.querySelector('.scorecard-meta-row');
507    if (wrap) wrap.hidden = !showImage;
508    if (table) table.style.display = showImage ? 'none' : '';
509    if (quote) quote.style.display = showImage ? 'none' : '';
510    if (meta) meta.style.display = showImage ? 'none' : '';
511  }
512
513  function openApiScorecard(idx) {
514    const item = apiScorecards[idx];
515    if (!item) return false;
516    const s = toApiScorecard(item);
517    const exam = document.getElementById('scoreExam');
518    const av = document.getElementById('scoreAvatar');
519    const title = document.getElementById('scorecardModalTitle');
520    const pct = document.getElementById('scorePct');
521    const image = document.getElementById('scoreFullImage');
522    const modal = document.getElementById('scorecardModal');
523
524    if (exam) exam.textContent = 'Score Card · ' + s.exam;
525    if (av) {
526      av.innerHTML = s.avatar
527        ? `<img src="${escapeHtml(s.avatar)}" alt="${escapeHtml(s.name)}">`
528        : escapeHtml(s.initials);
529    }
530    if (title) title.textContent = s.name;
531    if (pct) pct.innerHTML = escapeHtml(s.pct) + '<sup>%ile</sup>';
532    if (image) image.src = s.fullImage || s.cardImage || '';
533    setScorecardImageMode(Boolean(s.fullImage || s.cardImage));
534    if (modal) {
535      modal.classList.add('show');
536      document.body.style.overflow = 'hidden';
537    }
538    return true;
539  }
540
541  window.openScorecard = function (idx) {
542    if (openApiScorecard(idx)) return;
543    const s = STUDENT_SCORECARDS[idx];
544    if (!s) return;
545    const exam = document.getElementById('scoreExam');
546    const av = document.getElementById('scoreAvatar');
547    const title = document.getElementById('scorecardModalTitle');
548    const pct = document.getElementById('scorePct');
549    const tbody = document.getElementById('scoreSections');
550    const quote = document.getElementById('scoreQuote');
551    const converts = document.getElementById('scoreConverts');
552    const addl = document.getElementById('scoreAddl');
553    const modal = document.getElementById('scorecardModal');
554
555    if (exam) exam.textContent = 'Score Card · ' + s.exam;
556    if (av) av.textContent = s.initials;
557    if (title) title.textContent = s.name;
558    if (pct) pct.innerHTML = s.pct + '<sup>%ile</sup>';
559    if (tbody) {
560      let rows = s.sections.map(r => `<tr><td>${r[0]}</td><td>${r[1]}</td><td>${r[2]}</td></tr>`).join('');
561      rows += `<tr class="total"><td>Overall</td><td>${s.overall[0]}</td><td>${s.overall[1]}<sup>%ile</sup></td></tr>`;
562      tbody.innerHTML = rows;
563    }
564    if (quote) quote.textContent = '"' + s.quote + '"';
565    if (converts) converts.textContent = s.converts;
566    if (addl) addl.textContent = s.addl;
567    setScorecardImageMode(false);
568    if (modal) {
569      modal.classList.add('show');
570      document.body.style.overflow = 'hidden';
571    }
572  };
573
574  window.closeScorecardModal = function () {
575    const modal = document.getElementById('scorecardModal');
576    if (modal) modal.classList.remove('show');
577    const image = document.getElementById('scoreFullImage');
578    if (image) image.src = '';
579    document.body.style.overflow = '';
580  };
581
582  function fetchScorecardPage(page, collected) {
583    return fetch('/api/scorecards-proxy?base_course_id=56&page=' + page)
584      .then(res => {
585        if (!res.ok) throw new Error('Scorecards request failed');
586        return res.json();
587      })
588      .then(response => {
589        const pageData = response && response.data;
590        const rows = pageData && Array.isArray(pageData.data) ? pageData.data : [];
591        rows.forEach(row => collected.push(row));
592        if (pageData && pageData.current_page < pageData.last_page) {
593          return fetchScorecardPage(pageData.current_page + 1, collected);
594        }
595        return collected;
596      });
597  }
598
599  function loadApiScorecards() {
600    const track = document.getElementById('storiesTrack');
601    if (!track) return;
602
603    fetchScorecardPage(1, [])
604      .then(items => {
605        const usable = items.filter(item => {
606          const s = toApiScorecard(item);
607          return s.name && (s.fullImage || s.cardImage);
608        });
609        if (!usable.length) return;
610
611        apiScorecards = usable;
612        const cards = usable.map((item, index) => renderApiStoryCard(item, index, false)).join('');
613        const duplicateCards = usable.map((item, index) => renderApiStoryCard(item, index, true)).join('');
614        track.innerHTML = cards + duplicateCards;
615        track.style.setProperty('--visible-cards', usable.length);
616        initReveal();
617      })
618      .catch(err => {
619        console.warn('Scorecard API unavailable; using static scorecards.', err);
620      });
621  }
622
623  loadApiScorecards();
624
625  // Click backdrop to close + Esc
626  const sm = document.getElementById('scorecardModal');
627  if (sm) {
628    sm.addEventListener('click', function (e) {
629      if (e.target === this) window.closeScorecardModal();
630    });
631  }
632  document.addEventListener('keydown', function (e) {
633    if (e.key === 'Escape' && sm && sm.classList.contains('show')) {
634      window.closeScorecardModal();
635    }
636  });
637
638  // ── Enquiry Modal Auto-Show (with 5s delay) ──
639  ; (function initEnquiryModal() {
640    const modal = document.getElementById('enquiryModal');
641    window.openEnquiryModal = function () {};
642    window.closeEnquiryModal = function () {
643      document.body.style.overflow = '';
644    };
645    window.submitEnquiry = function (e) {
646      if (e) e.preventDefault();
647    };
648    if (modal) modal.remove();
649    return;
650
651    const form = document.getElementById('enquiryForm');
652    const success = document.getElementById('enquirySuccess');
653    let hasShown = false;
654    let dismissed = false;
655
656    try {
657      dismissed = sessionStorage.getItem('enquiryDismissed') === '1';
658    } catch (e) {
659      console.warn('sessionStorage not available');
660    }
661
662    window.openEnquiryModal = function () {
663      if (hasShown || dismissed) return;
664      modal.classList.add('show');
665      hasShown = true;
666      document.body.style.overflow = 'hidden';
667      if (form) form.reset();
668      if (form) form.classList.remove('hidden');
669      if (success) success.classList.remove('show');
670    };
671
672    window.closeEnquiryModal = function () {
673      modal.classList.remove('show');
674      document.body.style.overflow = '';
675      try {
676        sessionStorage.setItem('enquiryDismissed', '1');
677      } catch (e) { }
678    };
679
680    window.submitEnquiry = function (e) {
681      if (e) e.preventDefault();
682      if (form) form.classList.add('hidden');
683      if (success) success.classList.add('show');
684      try {
685        sessionStorage.setItem('enquiryDismissed', '1');
686      } catch (e) { }
687      setTimeout(window.closeEnquiryModal, 2800);
688    };
689
690    // Auto-popup disabled for now.
691    // setTimeout(() => {
692    //   if (typeof window.openEnquiryModal === 'function') {
693    //     window.openEnquiryModal();
694    //   }
695    // }, 5000);
696
697    // Backdrop click close
698    modal.addEventListener('click', (e) => {
699      if (e.target === modal) window.closeEnquiryModal();
700    });
701
702    // ESC key close
703    document.addEventListener('keydown', (e) => {
704      if (e.key === 'Escape' && modal.classList.contains('show')) {
705        window.closeEnquiryModal();
706      }
707    });
708  })();
709
710  // ── Stories marquee — arrow controls ──
711  ; (function () {
712    const track = document.getElementById('storiesTrack');
713    const prev = document.getElementById('storiesPrev');
714    const next = document.getElementById('storiesNext');
715    if (!track || !prev || !next) return;
716
717    const CARD_STEP = 336; // 320px card + 16px gap
718    let resumeTimer = null;
719
720    function advance(direction) {
721      track.classList.add('is-paused');
722      const computed = getComputedStyle(track).transform;
723      let currentX = 0;
724      if (computed && computed !== 'none') {
725        const match = computed.match(/matrix.*\((.+)\)/);
726        if (match) {
727          const parts = match[1].split(',').map(Number);
728          currentX = parts[parts.length - 2] || 0;
729        }
730      }
731      const newX = currentX + (direction * CARD_STEP * -1);
732      track.style.animation = 'none';
733      track.style.transform = 'translateX(' + newX + 'px)';
734      clearTimeout(resumeTimer);
735      resumeTimer = setTimeout(() => {
736        track.style.animation = '';
737        track.style.transform = '';
738        track.classList.remove('is-paused');
739      }, 4000);
740    }
741
742    prev.addEventListener('click', () => advance(-1));
743    next.addEventListener('click', () => advance(1));
744  })();
745
746  // ── Secrets marquee — arrow controls + YouTube modal ──
747  ; (function () {
748    const track = document.getElementById('secretsTrack');
749    const prev = document.getElementById('secretsPrev');
750    const next = document.getElementById('secretsNext');
751    if (!track || !prev || !next) return;
752
753    const CARD_STEP = 244; // 230px card + 14px gap
754    let resumeTimer = null;
755
756    function advance(direction) {
757      track.classList.add('is-paused');
758      const computed = getComputedStyle(track).transform;
759      let currentX = 0;
760      if (computed && computed !== 'none') {
761        const match = computed.match(/matrix.*\((.+)\)/);
762        if (match) {
763          const parts = match[1].split(',').map(Number);
764          currentX = parts[parts.length - 2] || 0;
765        }
766      }
767      const newX = currentX + (direction * CARD_STEP * -1);
768      track.style.animation = 'none';
769      track.style.transform = 'translateX(' + newX + 'px)';
770      clearTimeout(resumeTimer);
771      resumeTimer = setTimeout(() => {
772        track.style.animation = '';
773        track.style.transform = '';
774        track.classList.remove('is-paused');
775      }, 4000);
776    }
777
778    prev.addEventListener('click', () => advance(-1));
779    next.addEventListener('click', () => advance(1));
780
781    const wrap = track.closest('.secrets-marquee-wrap');
782    if (wrap) {
783      wrap.addEventListener('keydown', (e) => {
784        if (e.target.classList && e.target.classList.contains('secret-card')) return;
785        if (e.key === 'ArrowLeft') { advance(-1); e.preventDefault(); }
786        else if (e.key === 'ArrowRight') { advance(1); e.preventDefault(); }
787      });
788    }
789
790    // Click-to-play YouTube modal
791    function openYtModal(videoId) {
792      if (!videoId) return;
793      const frame = document.getElementById('ytFrame');
794      const modal = document.getElementById('ytModal');
795      if (frame) frame.src = 'https://www.youtube.com/embed/' + encodeURIComponent(videoId) + '?autoplay=1&rel=0';
796      if (modal) modal.classList.add('show');
797      document.body.style.overflow = 'hidden';
798    }
799    window.closeYtModal = function () {
800      const frame = document.getElementById('ytFrame');
801      const modal = document.getElementById('ytModal');
802      if (frame) frame.src = ''; // stops the playback
803      if (modal) modal.classList.remove('show');
804      document.body.style.overflow = '';
805    }
806
807    // Wire up every card (including duplicates)
808    document.querySelectorAll('.secret-card[data-yt]').forEach(card => {
809      card.addEventListener('click', () => openYtModal(card.dataset.yt));
810      card.addEventListener('keydown', (e) => {
811        if (e.key === 'Enter' || e.key === ' ') {
812          e.preventDefault();
813          openYtModal(card.dataset.yt);
814        }
815      });
816    });
817
818    // Click backdrop or Esc to close
819    const ytm = document.getElementById('ytModal');
820    if (ytm) {
821      ytm.addEventListener('click', (e) => {
822        if (e.target === ytm) window.closeYtModal();
823      });
824    }
825    document.addEventListener('keydown', (e) => {
826      if (e.key === 'Escape' && ytm && ytm.classList.contains('show')) {
827        window.closeYtModal();
828      }
829    });
830  })();
831
832  // ── Faculty marquee — arrow controls ──
833  ; (function () {
834    const track = document.getElementById('facultyTrack');
835    const prev = document.getElementById('facultyPrev');
836    const next = document.getElementById('facultyNext');
837    if (!track || !prev || !next) return;
838
839    // 230px card + 14px gap = 244px step; advance by 1 card per click
840    const CARD_STEP = 244;
841    let manualOffset = 0;
842    let resumeTimer = null;
843
844    function advance(direction) {
845      // Pause animation, take over with transform
846      track.classList.add('is-paused');
847      // Get the current rendered translateX from the running animation
848      const computed = getComputedStyle(track).transform;
849      let currentX = 0;
850      if (computed && computed !== 'none') {
851        const m = computed.match(/matrix.*\((.+)\)/);
852        if (m) {
853          const parts = m[1].split(',').map(Number);
854          // For 2D matrix: [a, b, c, d, tx, ty]
855          currentX = parts[parts.length - 2] || 0;
856        }
857      }
858      manualOffset = currentX + (direction * CARD_STEP * -1);
859      track.style.animation = 'none';
860      track.style.transform = 'translateX(' + manualOffset + 'px)';
861
862      // After a delay, hand back to the auto-scroll animation
863      clearTimeout(resumeTimer);
864      resumeTimer = setTimeout(() => {
865        track.style.animation = '';
866        track.style.transform = '';
867        track.classList.remove('is-paused');
868      }, 4000);
869    }
870
871    prev.addEventListener('click', () => advance(-1));
872    next.addEventListener('click', () => advance(1));
873
874    // Keyboard: arrow keys when wrap is focused
875    const wrap = track.closest('.faculty-marquee-wrap');
876    if (wrap) {
877      wrap.addEventListener('keydown', (e) => {
878        if (e.key === 'ArrowLeft') { advance(-1); e.preventDefault(); }
879        else if (e.key === 'ArrowRight') { advance(1); e.preventDefault(); }
880      });
881    }
882  })();
883
884  // ── Mega menu — vertical category cascade ──
885  ; (function () {
886    const cats = document.querySelectorAll('.mega-cat');
887    const panels = document.querySelectorAll('.mega-sub-panel');
888    if (!cats.length || !panels.length) return;
889
890    function activate(catKey) {
891      cats.forEach(c => c.classList.toggle('active', c.dataset.cat === catKey));
892      panels.forEach(p => p.classList.toggle('active', p.dataset.panel === catKey));
893    }
894
895    cats.forEach(c => {
896      c.addEventListener('mouseenter', () => activate(c.dataset.cat));
897      c.addEventListener('focus', () => activate(c.dataset.cat));
898      c.addEventListener('click', (e) => { e.preventDefault(); activate(c.dataset.cat); });
899    });
900  })();
901
902  // ── Courses section — tab switcher ──
903  ; (function () {
904    const tabs = document.querySelectorAll('.courses-tab');
905    const panels = document.querySelectorAll('.courses-panel');
906    if (!tabs.length || !panels.length) return;
907
908    function activate(catKey) {
909      tabs.forEach(t => {
910        const isActive = t.dataset.cat === catKey;
911        t.classList.toggle('active', isActive);
912        t.setAttribute('aria-selected', isActive ? 'true' : 'false');
913      });
914      panels.forEach(p => p.classList.toggle('active', p.dataset.panel === catKey));
915    }
916
917    tabs.forEach(t => {
918      t.addEventListener('click', (e) => { e.preventDefault(); activate(t.dataset.cat); });
919    });
920  })();
921
922  // ── Courses section — View all / Show less toggle ──
923  ; (function () {
924    const links = document.querySelectorAll('.courses-viewall-link');
925    if (!links.length) return;
926
927    const labels = {
928      cat: { collapsed: 'View all CAT courses →', expanded: '← Show less' },
929      noncat: { collapsed: 'View all Non-CAT courses →', expanded: '← Show less' },
930      mocks: { collapsed: 'View all Mocks →', expanded: '← Show less' }
931    };
932
933    links.forEach(link => {
934      link.addEventListener('click', (e) => {
935        e.preventDefault();
936        const target = link.dataset.target;
937        if (!target) return;
938        const panel = document.querySelector('.courses-panel[data-panel="' + target + '"]');
939        if (!panel) return;
940        const expanded = panel.classList.toggle('expanded');
941        const lbl = labels[target];
942        if (lbl) link.textContent = expanded ? lbl.expanded : lbl.collapsed;
943
944        if (!expanded) {
945          const section = panel.closest('.courses-section') || document.getElementById('courses');
946          if (section) {
947            requestAnimationFrame(() => {
948              section.scrollIntoView({ behavior: 'smooth', block: 'start' });
949            });
950          }
951        }
952      });
953    });
954  })();
955
956  // ── Exam Spotlight: click-to-load embedded YouTube player ──
957  // Each .exam-spotlight-video card holds a thumbnail facade. On click, swap
958  // for a live <iframe> with autoplay. Lightweight pattern — no upfront
959  // iframe cost, no Google tracking until the user actually plays.
960  ; (function () {
961    function loadEmbeddedPlayer(card) {
962      if (card.classList.contains('is-playing')) return;
963      const videoId = card.dataset.yt;
964      if (!videoId) return;
965      const iframe = document.createElement('iframe');
966      iframe.className = 'exam-spotlight-iframe';
967      iframe.src = 'https://www.youtube-nocookie.com/embed/' + videoId
968        + '?autoplay=1&rel=0&modestbranding=1&playsinline=1';
969      iframe.title = card.getAttribute('aria-label') || 'YouTube video player';
970      iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
971      iframe.referrerPolicy = 'strict-origin-when-cross-origin';
972      iframe.allowFullscreen = true;
973      card.appendChild(iframe);
974      card.classList.add('is-playing');
975    }
976
977    document.querySelectorAll('.exam-spotlight-video').forEach(card => {
978      card.addEventListener('click', (e) => {
979        // Don't trigger play if user clicked the YouTube redirect button
980        if (e.target.closest('.exam-spotlight-yt-link')) return;
981        loadEmbeddedPlayer(card);
982      });
983      card.addEventListener('keydown', (e) => {
984        if (e.key === 'Enter' || e.key === ' ') {
985          if (e.target.closest('.exam-spotlight-yt-link')) return;
986          e.preventDefault();
987          loadEmbeddedPlayer(card);
988        }
989      });
990    });
991  })();
992
993  window.openPdfModal = function () {
994    const pm = document.getElementById('pdfModal');
995    if (pm) pm.classList.add('show');
996    document.body.style.overflow = 'hidden';
997    showPdfList();
998  };
999  window.closePdfModal = function () {
1000    const pm = document.getElementById('pdfModal');
1001    if (pm) pm.classList.remove('show');
1002    document.body.style.overflow = '';
1003    // reset to list state for next open
1004    setTimeout(() => {
1005      showPdfList();
1006      const fm = document.getElementById('formMain'); if (fm) fm.style.display = '';
1007      const fs = document.getElementById('formSuccess'); if (fs) fs.classList.remove('show');
1008      const mi = document.getElementById('mobileInput'); if (mi) mi.value = '';
1009      const nameI = document.getElementById('nameInput'); if (nameI) nameI.value = '';
1010      const emailI = document.getElementById('emailInput'); if (emailI) emailI.value = '';
1011    }, 200);
1012  };
1013  // ── Login / SignUp Modal ──
1014  window.openLoginModal = function () {
1015    const lm = document.getElementById('loginModal');
1016    if (lm) lm.classList.add('show');
1017    document.body.style.overflow = 'hidden';
1018    setTimeout(() => {
1019      const i = document.getElementById('loginInput');
1020      if (i) i.focus();
1021    }, 100);
1022  };
1023  window.closeLoginModal = function () {
1024    const lm = document.getElementById('loginModal');
1025    if (lm) lm.classList.remove('show');
1026    document.body.style.overflow = '';
1027  };
1028  // Close on backdrop click
1029  /*; (function () {
1030    const lm = document.getElementById('loginModal');
1031    if (!lm) return;
1032    lm.addEventListener('click', (e) => {
1033      if (e.target === lm) window.closeLoginModal();
1034    });
1035    document.addEventListener('keydown', (e) => {
1036      if (e.key === 'Escape' && lm.classList.contains('show')) window.closeLoginModal();
1037    });
1038  })();*/
1039  // Close on backdrop click
1040  const pdfm = document.getElementById('pdfModal');
1041  if (pdfm) {
1042    pdfm.addEventListener('click', (e) => {
1043      if (e.target.id === 'pdfModal') window.closePdfModal();
1044    });
1045  }
1046  // Close on escape
1047  document.addEventListener('keydown', (e) => {
1048    if (e.key === 'Escape' && pdfm && pdfm.classList.contains('show')) window.closePdfModal();
1049  });
1050
1051  window.selectPdf = function (idx) {
1052    selectedPdf = PDF_LIST[idx];
1053    const pill = document.getElementById('selectedPdfPill');
1054    if (pill) pill.textContent = selectedPdf.name;
1055    showPdfForm();
1056  };
1057
1058  function showPdfList() {
1059    const hl = document.getElementById('pdfHeaderList');
1060    const hf = document.getElementById('pdfHeaderForm');
1061    const lv = document.getElementById('pdfListView');
1062    const fv = document.getElementById('pdfFormView');
1063    const fs = document.getElementById('formSuccess');
1064    const fm = document.getElementById('formMain');
1065
1066    if (hl) hl.style.display = '';
1067    if (hf) hf.style.display = 'none';
1068    if (lv) lv.classList.remove('hidden');
1069    if (fv) fv.classList.remove('active');
1070    if (fs) fs.classList.remove('show');
1071    if (fm) fm.style.display = '';
1072  }
1073
1074  function showPdfForm() {
1075    const hl = document.getElementById('pdfHeaderList');
1076    const hf = document.getElementById('pdfHeaderForm');
1077    const lv = document.getElementById('pdfListView');
1078    const fv = document.getElementById('pdfFormView');
1079
1080    if (hl) hl.style.display = 'none';
1081    if (hf) hf.style.display = '';
1082    if (lv) lv.classList.add('hidden');
1083    if (fv) fv.classList.add('active');
1084    const mi = document.getElementById('mobileInput');
1085    if (mi) setTimeout(() => mi.focus(), 100);
1086  }
1087
1088  window.toggleSignMode = function () {
1089    signMode = signMode === 'signin' ? 'signup' : 'signin';
1090    const nameI = document.getElementById('nameInput');
1091    const emailI = document.getElementById('emailInput');
1092    const toggleBtn = document.getElementById('toggleBtn');
1093    const formToggle = document.getElementById('formToggle');
1094    const formTitle = document.getElementById('formTitle');
1095    const submitBtn = document.getElementById('formSubmitBtn');
1096    if (signMode === 'signup') {
1097      if (nameI) nameI.style.display = '';
1098      if (emailI) emailI.style.display = '';
1099      if (toggleBtn) toggleBtn.textContent = 'Sign In';
1100      if (formToggle) formToggle.childNodes[0].nodeValue = 'Already a member? ';
1101      if (formTitle) formTitle.textContent = 'Create your free account';
1102      if (submitBtn) submitBtn.textContent = 'Sign Up & Download';
1103    } else {
1104      if (nameI) nameI.style.display = 'none';
1105      if (emailI) emailI.style.display = 'none';
1106      if (toggleBtn) toggleBtn.textContent = 'Sign Up';
1107      if (formToggle) formToggle.childNodes[0].nodeValue = 'New to CATKing? ';
1108      if (formTitle) formTitle.textContent = 'Sign in to download';
1109      if (submitBtn) submitBtn.textContent = 'Download PDF';
1110    }
1111  };
1112
1113  window.submitPdfForm = function () {
1114    const mi = document.getElementById('mobileInput');
1115    if (!mi) return;
1116    const mobile = mi.value.trim();
1117    if (!/^\d{10}$/.test(mobile)) {
1118      mi.style.borderColor = 'var(--red)';
1119      mi.focus();
1120      setTimeout(() => { mi.style.borderColor = ''; }, 1800);
1121      return;
1122    }
1123    if (signMode === 'signup') {
1124      const ni = document.getElementById('nameInput');
1125      const ei = document.getElementById('emailInput');
1126      if (!ni || !ei) return;
1127      const name = ni.value.trim();
1128      const email = ei.value.trim();
1129      if (!name || !/^\S+@\S+\.\S+$/.test(email)) {
1130        const target = !name ? ni : ei;
1131        target.style.borderColor = 'var(--red)';
1132        target.focus();
1133        setTimeout(() => { target.style.borderColor = ''; }, 1800);
1134        return;
1135      }
1136    }
1137    completeDownload();
1138  };
1139
1140  window.continueWithGoogle = function () {
1141    // Simulated Google flow — same success state
1142    completeDownload(true);
1143  };
1144
1145  function completeDownload(viaGoogle) {
1146    const fm = document.getElementById('formMain');
1147    const fs = document.getElementById('formSuccess');
1148    const msg = document.getElementById('successMsg');
1149    if (fm) fm.style.display = 'none';
1150    if (fs) fs.classList.add('show');
1151    if (msg) msg.textContent = selectedPdf
1152      ? `"${selectedPdf.name}" is downloading${viaGoogle ? ' (signed in via Google)' : ''}. Check your phone for the link.`
1153      : "Your PDF is downloading and you've been logged in.";
1154    // Trigger a real download (text placeholder so demo feels complete)
1155    if (selectedPdf) {
1156      const blob = new Blob([
1157        `CATKing — ${selectedPdf.name}\n\n` +
1158        `${selectedPdf.sub}\n\n` +
1159        `Thanks for downloading! This is a placeholder file — your real PDF will be sent to your registered mobile/email.\n\n` +
1160        `— CATKing Team`
1161      ], { type: 'text/plain' });
1162      const url = URL.createObjectURL(blob);
1163      const a = document.createElement('a');
1164      a.href = url;
1165      a.download = selectedPdf.name.replace(/\s+/g, '_') + '.txt';
1166      document.body.appendChild(a); a.click(); a.remove();
1167      setTimeout(() => URL.revokeObjectURL(url), 2000);
1168    }
1169    // Auto-close after a bit
1170    setTimeout(() => { window.closePdfModal(); }, 2800);
1171  }
1172
1173  // Numeric only for mobile field
1174  const mi = document.getElementById('mobileInput');
1175  if (mi) {
1176    mi.addEventListener('input', (e) => {
1177      e.target.value = e.target.value.replace(/\D/g, '').slice(0, 10);
1178    });
1179  }
1180
1181  // ── FAQ Toggling logic ──
1182  window.toggleFaq = function (btn) {
1183    const item = btn.closest('.faq-item');
1184    if (!item) return;
1185    const wasOpen = item.classList.contains('open');
1186
1187    // Close other items
1188    document.querySelectorAll('.faq-item').forEach(el => el.classList.remove('open'));
1189
1190    if (!wasOpen) {
1191      item.classList.add('open');
1192    }
1193  };
1194
1195})();

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.