PageSourceSearch

https://deepnodestudios.net/warrantytracking/

html deepnodestudios.net collected 2026-10-02 02:01:17 UTC 32,239 bytes, 809 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title data-i18n="meta_title">Warranty and Invoice Tracking | Deepnode Studios</title>
7    <meta id="meta-description" name="description" content="Manage warranties, invoices, and inventory records in one place with Warranty and Invoice Tracking, including reminders, OCR, barcode scanning, and optional cloud backup." data-i18n="meta_description" />
8    <meta id="meta-og-title" property="og:title" content="Warranty and Invoice Tracking | Deepnode Studios" data-i18n="meta_og_title">
9    <meta id="meta-og-description" property="og:description" content="An Android tool that keeps warranty dates, invoice attachments, serial number scans, and reminders in one workflow." data-i18n="meta_og_description" />
10    <meta property="og:type" content="website">
11    <meta property="og:url" content="https://deepnodestudios.net/warrantytracking/">
12    <meta property="og:image" content="https://deepnodestudios.net/warrantytracking/assets/img/app-icon.png">
13    <meta property="og:image:alt" content="Warranty and Invoice Tracking app icon">
14    <meta name="twitter:card" content="summary">
15    <meta name="twitter:title" content="Warranty and Invoice Tracking | Deepnode Studios" data-i18n="meta_og_title">
16    <meta name="twitter:description" content="An Android tool that keeps warranty dates, invoice attachments, serial number scans, and reminders in one workflow." data-i18n="meta_og_description">
17    <meta name="twitter:image" content="https://deepnodestudios.net/warrantytracking/assets/img/app-icon.png">
18    <link rel="canonical" href="https://deepnodestudios.net/warrantytracking/">
19    <link rel="icon" type="image/png" href="/warrantytracking/assets/img/app-icon.png">
20    <link rel="preconnect" href="https://fonts.googleapis.com">
21    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
22    <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
23    <style>
24
25        :root {
26            --bg: #f8fafc;
27            --surface: rgba(255, 255, 255, 0.85);
28            --surface-strong: #ffffff;
29            --border: rgba(99, 102, 241, 0.15);
30            --text: #0f172a;
31            --muted: #64748b;
32            --accent: #6366f1;
33            --accent-strong: #4338ca;
34            --accent-soft: #e0e7ff;
35            --teal: #0d9488;
36            --shadow: 0 24px 50px rgba(15, 23, 42, 0.06);
37        }
38
39        * {
40            box-sizing: border-box;
41        }
42
43        html {
44            scroll-behavior: smooth;
45        }
46
47        body {
48            margin: 0;
49            font-family: "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
50            color: var(--text);
51            background:
52                radial-gradient(circle at top left, rgba(99, 102, 241, 0.1), transparent 35%),
53                radial-gradient(circle at right 10%, rgba(13, 148, 136, 0.05), transparent 30%),
54                var(--bg);
55            min-height: 100vh;
56        }
57
58        a {
59            color: inherit;
60            text-decoration: none;
61        }
62
63        .page {
64            width: min(1120px, calc(100% - 32px));
65            margin: 0 auto;
66            padding: 24px 0 64px;
67        }
68
69        .topbar {
70            display: flex;
71            align-items: center;
72            justify-content: space-between;
73            gap: 16px;
74            margin-bottom: 28px;
75            padding: 14px 18px;
76            border: 1px solid var(--border);
77            border-radius: 22px;
78            background: rgba(255, 255, 255, 0.72);
79            backdrop-filter: blur(18px);
80            box-shadow: 0 16px 44px rgba(49, 46, 129, 0.05);
81        }
82
83        .brand {
84            display: inline-flex;
85            align-items: center;
86            gap: 12px;
87            font-weight: 700;
88            letter-spacing: 0.02em;
89        }
90
91        .brand-mark {
92            width: 36px;
93            height: 36px;
94            border-radius: 10px;
95            -o-object-fit: contain;
96               object-fit: contain;
97            box-shadow: 0 8px 16px rgba(99, 102, 241, 0.2);
98        }
99
100        .nav {
101            display: flex;
102            align-items: center;
103            gap: 12px;
104            flex-wrap: wrap;
105            justify-content: flex-end;
106        }
107
108        .locale-switcher {
109            display: inline-flex;
110            align-items: center;
111            gap: 10px;
112            margin-left: 4px;
113        }
114
115        .locale-label {
116            font-size: 0.88rem;
117            font-weight: 700;
118            color: var(--muted);
119        }
120
121        .locale-options {
122            display: inline-flex;
123            align-items: center;
124            padding: 4px;
125            border-radius: 999px;
126            background: rgba(255, 255, 255, 0.64);
127            border: 1px solid rgba(67, 56, 202, 0.12);
128        }
129
130        .locale-option {
131            border: 0;
132            background: transparent;
133            color: var(--muted);
134            font: inherit;
135            font-size: 0.82rem;
136            font-weight: 800;
137            letter-spacing: 0.06em;
138            padding: 8px 12px;
139            border-radius: 999px;
140            cursor: pointer;
141            transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
142        }
143
144        .locale-option:hover {
145            transform: translateY(-1px);
146        }
147
148        .locale-option.is-active {
149            background: linear-gradient(135deg, var(--accent), var(--accent-strong));
150            color: #ffffff;
151            box-shadow: 0 8px 18px rgba(55, 48, 163, 0.18);
152        }
153
154        .nav-link,
155        .button,
156        .button-secondary {
157            border-radius: 999px;
158            padding: 11px 16px;
159            font-weight: 700;
160            font-size: 0.95rem;
161            transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
162        }
163
164        .nav-link {
165            color: var(--muted);
166        }
167
168        .nav-link:hover,
169        .button:hover,
170        .button-secondary:hover {
171            transform: translateY(-1px);
172        }
173
174        .button {
175            background: linear-gradient(135deg, var(--accent), var(--accent-strong));
176            color: #ffffff;
177            box-shadow: 0 12px 28px rgba(55, 48, 163, 0.24);
178        }
179
180        .button-secondary {
181            background: rgba(238, 242, 255, 0.72);
182            border: 1px solid rgba(67, 56, 202, 0.14);
183            color: var(--accent-strong);
184        }
185
186        .hero {
187            display: grid;
188            grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);
189            gap: 28px;
190            align-items: stretch;
191        }
192
193        .hero-panel,
194        .card,
195        .mini-card {
196            background: var(--surface);
197            border: 1px solid var(--border);
198            border-radius: 28px;
199            box-shadow: var(--shadow);
200            backdrop-filter: blur(18px);
201        }
202
203        .hero-panel {
204            padding: 34px;
205        }
206
207        .eyebrow {
208            display: inline-flex;
209            align-items: center;
210            gap: 8px;
211            padding: 8px 14px;
212            border-radius: 999px;
213            background: rgba(13, 148, 136, 0.11);
214            color: var(--teal);
215            font-weight: 800;
216            letter-spacing: 0.05em;
217            text-transform: uppercase;
218            font-size: 0.8rem;
219        }
220
221        h1 {
222            margin: 18px 0 14px;
223            font-size: clamp(3rem, 6.5vw, 5.5rem);
224            line-height: 0.95;
225            letter-spacing: -0.04em;
226            background: linear-gradient(135deg, var(--text), var(--accent-strong));
227            -webkit-background-clip: text;
228            -webkit-text-fill-color: transparent;
229            font-weight: 900;
230        }
231
232        .lead {
233            margin: 0;
234            font-size: 1.12rem;
235            line-height: 1.7;
236            color: var(--muted);
237            max-width: 60ch;
238        }
239
240        .chip-row {
241            display: flex;
242            flex-wrap: wrap;
243            gap: 10px;
244            margin: 26px 0 30px;
245        }
246
247        .chip {
248            padding: 10px 14px;
249            border-radius: 999px;
250            background: rgba(255, 255, 255, 0.7);
251            border: 1px solid rgba(79, 70, 229, 0.14);
252            font-size: 0.93rem;
253            color: #312e81;
254        }
255
256        .cta-row {
257            display: flex;
258            flex-wrap: wrap;
259            gap: 12px;
260        }
261
262        .hero-side {
263            display: grid;
264            gap: 18px;
265        }
266
267        .section {
268            margin-top: 28px;
269        }
270
271        .section-title {
272            font-size: 1.65rem;
273            margin: 0 0 14px;
274            letter-spacing: -0.03em;
275        }
276
277        .section-copy {
278            margin: 0;
279            color: var(--muted);
280            line-height: 1.7;
281        }
282
283        .grid {
284            display: grid;
285            grid-template-columns: repeat(2, minmax(0, 1fr));
286            gap: 18px;
287            margin-top: 22px;
288        }
289
290        .card {
291            padding: 24px;
292        }
293
294        .card h3 {
295            margin: 0 0 10px;
296            font-size: 1.16rem;
297            letter-spacing: -0.03em;
298        }
299
300        .card p,
301        .card li {
302            color: var(--muted);
303            line-height: 1.65;
304        }
305
306        .card ul {
307            margin: 0;
308            padding-left: 18px;
309        }
310
311        .screenshots-container {
312            display: flex; 
313            gap: 20px; 
314            overflow-x: auto; 
315            padding: 24px 8px; 
316            scroll-snap-type: x mandatory;
317            scrollbar-width: none; /* Firefox */
318        }
319        
320        .screenshots-container::-webkit-scrollbar { 
321            display: none; /* Safari and Chrome */ 
322        }
323
324        .screenshot-img {
325            height: 460px; 
326            border-radius: 24px; 
327            scroll-snap-align: start; 
328            box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
329            transition: transform 0.3s ease, box-shadow 0.3s ease;
330            border: 1px solid rgba(255,255,255,0.4);
331        }
332
333        .screenshot-img:hover {
334            transform: translateY(-4px);
335            box-shadow: 0 20px 40px rgba(15, 23, 42, 0.18);
336        }
337
338        .footer {
339            margin-top: 32px;
340            padding: 22px 0 0;
341            border-top: 1px solid rgba(67, 56, 202, 0.14);
342            color: var(--muted);
343            display: flex;
344            justify-content: space-between;
345            gap: 16px;
346            flex-wrap: wrap;
347        }
348
349        @media (max-width: 920px) {
350            .hero,
351            .grid {
352                grid-template-columns: 1fr;
353            }
354
355            .hero-panel,
356            .card {
357                padding: 24px;
358            }
359        }
360
361        @media (max-width: 680px) {
362            .page {
363                width: min(100% - 20px, 1120px);
364                padding-top: 14px;
365            }
366
367            .topbar {
368                padding: 14px;
369            }
370
371            h1 {
372                font-size: 2.5rem;
373            }
374        }
375    
376    
377        
378        html.dark {
379            --bg: #0f172a;
380            --surface: rgba(30, 41, 59, 0.85);
381            --surface-strong: #1e293b;
382            --border: rgba(99, 102, 241, 0.25);
383            --text: #f8fafc;
384            --muted: #94a3b8;
385            --accent: #818cf8;
386            --accent-strong: #6366f1;
387            --accent-soft: #1e1b4b;
388            --teal: #14b8a6;
389            --shadow: 0 24px 50px rgba(0, 0, 0, 0.3);
390        }
391        
392        html.dark body {
393            background:
394                radial-gradient(circle at top left, rgba(99, 102, 241, 0.15), transparent 35%),
395                radial-gradient(circle at right 10%, rgba(13, 148, 136, 0.1), transparent 30%),
396                var(--bg) !important;
397        }
398
399        html.dark .topbar {
400            background: rgba(30, 41, 59, 0.72);
401        }
402        
403        html.dark .locale-options {
404            background: rgba(15, 23, 42, 0.64);
405            border: 1px solid rgba(129, 140, 248, 0.2);
406        }
407
408        html.dark .button-secondary {
409            background: rgba(30, 27, 75, 0.72);
410            border: 1px solid rgba(99, 102, 241, 0.3);
411            color: var(--accent);
412        }
413        
414        html.dark .chip {
415            background: rgba(30, 41, 59, 0.7);
416            border: 1px solid rgba(129, 140, 248, 0.2);
417            color: #c7d2fe;
418        }
419
420        .hidden { display: none !important; }
421        .lightbox-overlay {
422            position: fixed;
423            top: 0; left: 0; right: 0; bottom: 0;
424            background: rgba(0, 0, 0, 0.85);
425            backdrop-filter: blur(5px);
426            opacity: 0;
427            visibility: hidden;
428            transition: opacity 0.3s ease, visibility 0.3s ease;
429            z-index: 9998;
430            display: flex;
431            align-items: center;
432            justify-content: space-between;
433            padding: 0 20px;
434        }
435
436        .lightbox-overlay.active {
437            opacity: 1;
438            visibility: visible;
439        }
440
441        .lightbox-btn {
442            background: rgba(255, 255, 255, 0.1);
443            border: 1px solid rgba(255, 255, 255, 0.2);
444            color: white;
445            width: 50px;
446            height: 50px;
447            border-radius: 50%;
448            display: flex;
449            align-items: center;
450            justify-content: center;
451            cursor: pointer;
452            z-index: 10000;
453            transition: all 0.2s;
454            opacity: 0;
455            visibility: hidden;
456            font-size: 1.5rem;
457        }
458
459        .lightbox-overlay.active .lightbox-btn {
460            opacity: 1;
461            visibility: visible;
462        }
463
464        .lightbox-btn:hover {
465            background: rgba(255, 255, 255, 0.25);
466            transform: scale(1.1);
467        }
468
469        .lightbox-clone {
470            position: fixed;
471            z-index: 9999;
472            -o-object-fit: contain;
473               object-fit: contain;
474            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
475            border-radius: 24px;
476            cursor: zoom-out;
477        }
478
479        .lightbox-clone.active {
480            top: 50% !important;
481            left: 50% !important;
482            transform: translate(-50%, -50%);
483            width: 90vw !important;
484            height: 90vh !important;
485            border-radius: 12px;
486        }
487
488    </style>
489
490    <!-- Font Awesome -->
491    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
492    <!-- Theme Script -->
493    
493<script>
494      if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
495          document.documentElement.classList.add('dark');
496      } else {
497          document.documentElement.classList.remove('dark');
498      }
499    </script>
499
500  
500<script type="module" crossorigin src="/assets/warrantytracking-PILnlCO6.js"></script>
500
501  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-EeOZK34R.js">
502  <link rel="modulepreload" crossorigin href="/assets/chunk-BOCXvY9V.js">
503  <link rel="modulepreload" crossorigin href="/assets/i18n-Do7Bh9wB.js">
504</head>
505<body data-page="index">
506    <main class="page">
507        <header class="topbar">
508            <a class="brand" href="/" aria-label="Deepnode Studios home page" data-i18n-aria-label="brand_aria">
509                <img src="/assets/icon/app_icon.png" alt="Deepnode Studios Logo" class="brand-mark">
510                <span>Deepnode Studios</span>
511            </a>
512            <nav class="nav">
513                <a class="nav-link" href="/warrantytracking/privacy.html" data-i18n="nav_privacy">Privacy Policy</a>
514                <div class="locale-switcher" role="group" aria-label="Language selection" data-i18n-aria-label="language_switcher_aria">
515                    <span class="locale-label" data-i18n="language_label">Language</span>
516                    <div class="locale-options">
517                        <button type="button" class="locale-option is-active" data-locale="en" aria-pressed="true">EN</button>
518                        <button type="button" class="locale-option" data-locale="tr" aria-pressed="false">TR</button>
519                    </div>
520                </div>
521            
522                <button id="theme-toggle" style="background: transparent; border: none; cursor: pointer; color: var(--muted); font-size: 1.2rem; display: inline-flex; align-items: center; justify-content: center; margin-left: 12px; padding: 6px; transition: color 0.2s;" title="Tema Değiştir">
523                    <i id="theme-toggle-dark-icon" class="fas fa-moon hidden"></i>
524                    <i id="theme-toggle-light-icon" class="fas fa-sun hidden"></i>
525                </button>
526            </nav>
527        </header>
528
529        <section class="hero">
530            <article class="hero-panel">
531                <span class="eyebrow" data-i18n="hero_eyebrow">Android utility</span>
532                <h1 data-i18n-html="hero_title_html">Warranty and Invoice<br>Tracking</h1>
533                <p class="lead" data-i18n="hero_lead">
534                    Keep warranty periods, invoice attachments, serial numbers, and product details in one place with a practical tracker that stays local-first and optionally supports cloud backup.
535                </p>
536                <div class="chip-row" aria-label="Features" data-i18n-aria-label="chips_aria" data-i18n-html="chips_html">
537                    <span class="chip">Warranty expiry reminders</span>
538                    <span class="chip">Invoice photo/PDF attachments</span>
539                    <span class="chip">OCR-assisted field prefills</span>
540                    <span class="chip">Barcode and serial scanning</span>
541                    <span class="chip">JSON/CSV export</span>
542                    <span class="chip">Premium cloud backup</span>
543                </div>
544                <div class="cta-row">
545                    <a class="button" href="https://play.google.com/store/apps/details?id=com.deepnode.warrantytracking" target="_blank" rel="noopener noreferrer" data-i18n="cta_primary">Get on Google Play</a>
546                    <a class="button-secondary" href="/cdn-cgi/l/email-protection#1a7e7f7f6a74757e7f696e6f7e7375695a7d777b737634797577" data-i18n="cta_contact">Contact</a>
547                </div>
548            </article>
549
550            <aside class="hero-side">
551                <section class="card" style="height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; gap: 16px;">
552                     <img src="/warrantytracking/assets/img/app-icon.png" alt="Warranty and Invoice Tracking App Icon" style="width: 120px; height: 120px; border-radius: 30px; box-shadow: 0 20px 40px rgba(79, 70, 229, 0.3);">
553                     <h3 style="margin: 0; font-size: 1.5rem; color: var(--accent-strong);" data-i18n-html="hero_title_html">Warranty and Invoice<br>Tracking</h3>
554                </section>
555            </aside>
556        </section>
557
558        <section class="section">
559            <h2 class="section-title" data-i18n="about_title">What does the app do?</h2>
560            <p class="section-copy" data-i18n="about_desc">
561                Warranty and Invoice Tracking helps you organize purchase dates, warranty terms, brand and model details, and related documents for electronics, appliances, accessories, and work equipment. Reminders help you avoid missing important dates, while OCR and barcode scanning reduce manual entry.
562            </p>
563            <div class="grid">
564                <article class="card">
565                    <h3 data-i18n="card_local_title">Local-first inventory</h3>
566                    <p data-i18n="card_local_desc">
567                        Your records are kept on-device with Isar. Even in local-only mode, you can manage product names, warranty periods, serial numbers, and notes.
568                    </p>
569                </article>
570                <article class="card">
571                    <h3 data-i18n="card_docs_title">Add documents faster</h3>
572                    <p data-i18n="card_docs_desc">
573                        Add invoices from the camera, gallery, or a PDF picker, then use OCR to prefill editable fields such as date, amount, brand, and serial number.
574                    </p>
575                </article>
576            </div>
577        </section>
578
579        <section class="section">
580            <h2 class="section-title" data-i18n="screenshots_title">App Screenshots</h2>
581            <div class="screenshots-container">
582                <img id="screenshot-1" src="/warrantytracking/assets/img/en/01-onboarding.webp" alt="Onboarding" class="screenshot-img" width="1344" height="2992" loading="lazy" decoding="async">
583                <img id="screenshot-2" src="/warrantytracking/assets/img/en/02-home.webp" alt="Home Screen" class="screenshot-img" width="1344" height="2992" loading="lazy" decoding="async">
584                <img id="screenshot-3" src="/warrantytracking/assets/img/en/03-inventory.webp" alt="Inventory" class="screenshot-img" width="1344" height="2992" loading="lazy" decoding="async">
585                <img id="screenshot-4" src="/warrantytracking/assets/img/en/04-settings.webp" alt="Settings" class="screenshot-img" width="1344" height="2992" loading="lazy" decoding="async">
586            </div>
587        </section>
588
589        <footer class="footer">
590            <span data-i18n="footer_title">Deepnode Studios - Warranty and Invoice Tracking</span>
591            <span data-i18n-html="footer_links_html"><a href="/warrantytracking/privacy.html">Privacy Policy</a> · <a href="/cdn-cgi/l/email-protection#d6b2b3b3a6b8b9b2b3a5a2a3b2bfb9a596b1bbb7bfbaf8b5b9bb"><span class="__cf_email__" data-cfemail="1e7a7b7b6e70717a7b6d6a6b7a77716d5e79737f7772307d7173">[email&#160;protected]</span></a></span>
592        </footer>
593    </main>
vendor: 115 bytes, lines 593-594
593�
594    <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
594<script>
595      const updateScreenshots = () => {
596        const lang = document.documentElement.lang || 'en';
597        document.getElementById('screenshot-1').src = `./assets/img/${lang}/01-onboarding.webp`;
598        document.getElementById('screenshot-2').src = `./assets/img/${lang}/02-home.webp`;
599        document.getElementById('screenshot-3').src = `./assets/img/${lang}/03-inventory.webp`;
600        document.getElementById('screenshot-4').src = `./assets/img/${lang}/04-settings.webp`;
601      };
602      const observer = new MutationObserver((mutations) => {
603        mutations.forEach((mutation) => {
604          if (mutation.attributeName === 'lang') {
605            updateScreenshots();
606          }
607        });
608      });
609      observer.observe(document.documentElement, { attributes: true });
610    </script>
610
611
612    
612<script>
613      document.addEventListener("DOMContentLoaded", function() {
614          const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
615          const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
616          const themeToggleBtn = document.getElementById('theme-toggle');
617
618          if (!themeToggleBtn) return;
619
620          if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
621              themeToggleLightIcon.classList.remove('hidden');
622          } else {
623              themeToggleDarkIcon.classList.remove('hidden');
624          }
625
626          themeToggleBtn.addEventListener('click', function() {
627              themeToggleDarkIcon.classList.toggle('hidden');
628              themeToggleLightIcon.classList.toggle('hidden');
629
630              if (localStorage.getItem('color-theme')) {
631                  if (localStorage.getItem('color-theme') === 'light') {
632                      document.documentElement.classList.add('dark');
633                      localStorage.setItem('color-theme', 'dark');
634                  } else {
635                      document.documentElement.classList.remove('dark');
636                      localStorage.setItem('color-theme', 'light');
637                  }
638              } else {
639                  if (document.documentElement.classList.contains('dark')) {
640                      document.documentElement.classList.remove('dark');
641                      localStorage.setItem('color-theme', 'light');
642                  } else {
643                      document.documentElement.classList.add('dark');
644                      localStorage.setItem('color-theme', 'dark');
645                  }
646              }
647          });
648      });
649    </script>
649
650
651            
651<script>
652      document.addEventListener("DOMContentLoaded", function() {
653          const images = Array.from(document.querySelectorAll('.screenshot-img'));
654          if (images.length === 0) return;
655
656          const overlay = document.createElement('div');
657          overlay.className = 'lightbox-overlay';
658          
659          const prevBtn = document.createElement('button');
660          prevBtn.className = 'lightbox-btn';
661          prevBtn.innerHTML = '<i class="fas fa-chevron-left"></i>';
662          
663          const nextBtn = document.createElement('button');
664          nextBtn.className = 'lightbox-btn';
665          nextBtn.innerHTML = '<i class="fas fa-chevron-right"></i>';
666
667          overlay.appendChild(prevBtn);
668          overlay.appendChild(nextBtn);
669          document.body.appendChild(overlay);
670
671          let currentClone = null;
672          let originalImg = null;
673          let currentIndex = 0;
674
675          const updateCloneSource = (index, direction) => {
676              currentIndex = index;
677              originalImg = images[currentIndex];
678              if (currentClone) {
679                  currentClone.style.opacity = '0';
680                  currentClone.style.transform = `translate(calc(-50% + ${direction === 'next' ? '-30px' : '30px'}), -50%) scale(0.98)`;
681                  
682                  setTimeout(() => {
683                      currentClone.src = originalImg.src;
684                      
685                      currentClone.style.transition = 'none';
686                      currentClone.style.transform = `translate(calc(-50% + ${direction === 'next' ? '30px' : '-30px'}), -50%) scale(0.98)`;
687                      
688                      void currentClone.offsetWidth; // force reflow
689                      
690                      currentClone.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
691                      currentClone.style.opacity = '1';
692                      currentClone.style.transform = 'translate(-50%, -50%) scale(1)';
693                  }, 200); // snappy transition
694              }
695          };
696
697          const showNext = (e) => {
698              if (e) e.stopPropagation();
699              let newIndex = currentIndex + 1;
700              if (newIndex >= images.length) newIndex = 0;
701              updateCloneSource(newIndex, 'next');
702          };
703
704          const showPrev = (e) => {
705              if (e) e.stopPropagation();
706              let newIndex = currentIndex - 1;
707              if (newIndex < 0) newIndex = images.length - 1;
708              updateCloneSource(newIndex, 'prev');
709          };
710
711          prevBtn.addEventListener('click', showPrev);
712          nextBtn.addEventListener('click', showNext);
713
714          images.forEach((img, index) => {
715              img.style.cursor = 'zoom-in';
716              img.addEventListener('click', () => {
717                  if (currentClone) return;
718
719                  currentIndex = index;
720                  originalImg = img;
721                  const rect = img.getBoundingClientRect();
722
723                  currentClone = document.createElement('img');
724                  currentClone.src = img.src;
725                  currentClone.className = 'lightbox-clone';
726                  
727                  currentClone.style.top = rect.top + 'px';
728                  currentClone.style.left = rect.left + 'px';
729                  currentClone.style.width = rect.width + 'px';
730                  currentClone.style.height = rect.height + 'px';
731                  
732                  document.body.appendChild(currentClone);
733                  overlay.classList.add('active');
734                  
735                  requestAnimationFrame(() => {
736                      requestAnimationFrame(() => {
737                          currentClone.classList.add('active');
738                      });
739                  });
740              });
741          });
742
743          const closeLightbox = () => {
744              if (!currentClone) return;
745              overlay.classList.remove('active');
746              
747              const rect = originalImg.getBoundingClientRect();
748              currentClone.style.top = rect.top + 'px';
749              currentClone.style.left = rect.left + 'px';
750              currentClone.style.width = rect.width + 'px';
751              currentClone.style.height = rect.height + 'px';
752              currentClone.classList.remove('active');
753
754              setTimeout(() => {
755                  if (currentClone) {
756                      currentClone.remove();
757                      currentClone = null;
758                  }
759                  originalImg = null;
760              }, 300);
761          };
762
763          overlay.addEventListener('click', (e) => {
764              if (e.target === overlay) closeLightbox();
765          });
766          
767          document.body.addEventListener('click', (e) => {
768              if (e.target.classList.contains('lightbox-clone')) {
769                  closeLightbox();
770              }
771          });
772
773          document.addEventListener('keydown', (e) => {
774              if (!currentClone) return;
775              if (e.key === 'Escape') closeLightbox();
776              if (e.key === 'ArrowRight') showNext();
777              if (e.key === 'ArrowLeft') showPrev();
778          });
779
780          let touchStartX = 0;
781          let touchStartY = 0;
782          
783          document.addEventListener('touchstart', (e) => {
784              if (!currentClone) return;
785              touchStartX = e.changedTouches[0].screenX;
786              touchStartY = e.changedTouches[0].screenY;
787          }, { passive: true });
788          
789          document.addEventListener('touchend', (e) => {
790              if (!currentClone) return;
791              const touchEndX = e.changedTouches[0].screenX;
792              const touchEndY = e.changedTouches[0].screenY;
793              
794              const deltaX = touchEndX - touchStartX;
795              const deltaY = touchEndY - touchStartY;
796              
797              if (Math.abs(deltaX) > 40 && Math.abs(deltaX) > Math.abs(deltaY)) {
798                  if (deltaX < 0) {
799                      showNext();
800                  } else {
801                      showPrev();
802                  }
803              }
804          }, { passive: true });
805      });
806    </script>
806
807
808<!-- Cloudflare Pages Analytics -->
vendor: 99 bytes, line 808
808<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "
808eeb2bc3a74c641b8831749c26b69cc5b
vendor: 13 bytes, line 808
808"}'></script>
808<!-- Cloudflare Pages Analytics -->
808<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a4401e4bb8bb226d',t:'MTc5MDkwNjQ3Nw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
808</body>
809</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.