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 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.