2025-08-27 13:34:10 +08:00

473 lines
35 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Search results · Leitan GPS</title>
<meta name="description" content="Site-wide search results for products, manuals, firmware/software, knowledge base and videos." />
<meta name="theme-color" content="#0ea5e9" />
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='%230ea5e9'/%3E%3Cpath d='M50 20a30 30 0 1030 30A30 30 0 0050 20zm0 10a20 20 0 11-20 20A20 20 0 0150 30z' fill='white'/%3E%3C/svg%3E" />
<!-- Tailwind v4 Play CDN -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script type="tailwind-config">
export default {
darkMode: 'class',
theme: { extend: { colors: { brand: { 50:'#eff6ff',100:'#dbeafe',200:'#bfdbfe',300:'#93c5fd',400:'#60a5fa',500:'#3b82f6',600:'#2563eb',700:'#1d4ed8',800:'#1e40af',900:'#1e3a8a' }}, boxShadow: { soft: '0 10px 30px rgba(0,0,0,0.06)' } } }
}
</script>
<style>
html { scroll-behavior: smooth; }
::selection { background: #bfdbfe; }
mark { background: #fde68a; color: inherit; padding: 0 .15em; border-radius: .2em; }
</style>
</head>
<body class="bg-white text-gray-800 antialiased dark:bg-gray-950 dark:text-gray-100">
<!-- Decorative BG -->
<div aria-hidden="true" class="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
<div class="absolute -top-32 left-1/2 h-80 w-[1100px] -translate-x-1/2 rounded-full bg-gradient-to-r from-brand-400/30 via-sky-400/20 to-cyan-400/30 blur-3xl dark:from-brand-600/30 dark:via-sky-600/20 dark:to-cyan-600/30"></div>
</div>
<!-- Header -->
<header id="top" class="sticky top-0 z-50 backdrop-blur supports-[backdrop-filter]:bg-white/60 bg-white/80 border-b border-gray-200/70 dark:bg-gray-950/70 dark:border-gray-800">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<a href="/index.html" class="flex items-center gap-2">
<span class="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-brand-600 text-white shadow-soft">L</span>
<span class="font-semibold">Leitan GPS</span>
</a>
<nav class="hidden md:flex items-center gap-8 text-sm">
<a href="/support/index.html" class="hover:text-brand-600 dark:hover:text-brand-400">Support</a>
<a href="/products/index.html" class="hover:text-brand-600 dark:hover:text-brand-400">Catalog</a>
<span class="h-5 w-px bg-gray-300/60 dark:bg-gray-700"></span>
<a href="/employee/index.html" class="hover:text-brand-600 dark:hover:text-brand-400">Employee</a>
<span class="h-5 w-px bg-gray-300/60 dark:bg-gray-700"></span>
<div class="flex items-center gap-3" role="group" aria-label="Language">
<button type="button" class="text-xs px-2 py-1 rounded border border-gray-300/80 hover:border-brand-500 hover:text-brand-600 dark:border-gray-700 dark:hover:border-brand-500" title="Italiano (not implemented)">IT</button>
<button type="button" class="text-xs px-2 py-1 rounded border border-brand-500 text-brand-700 dark:text-brand-300" aria-pressed="true" title="English">EN</button>
</div>
</nav>
<div class="flex items-center gap-3">
<span id="empBadge" class="hidden sm:inline-flex items-center gap-1 rounded-full border border-gray-300 px-2 py-1 text-xs text-gray-600 dark:border-gray-700 dark:text-gray-300"></span>
<button id="themeToggle" aria-label="Toggle theme" class="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-gray-300 bg-white shadow-sm hover:border-brand-500 dark:bg-gray-900 dark:border-gray-700">
<svg id="iconSun" class="h-5 w-5 hidden" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2m0 14v2m9-9h-2M5 12H3m15.364-6.364l-1.414 1.414M7.05 16.95l-1.414 1.414m0-11.314l1.414 1.414M16.95 16.95l1.414 1.414"/><circle cx="12" cy="12" r="4"/></svg>
<svg id="iconMoon" class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
</button>
</div>
</div>
</div>
</header>
<!-- Search header -->
<section class="relative">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="grid lg:grid-cols-[1.25fr_0.75fr] gap-10 py-10 lg:py-14 items-center">
<div>
<p class="inline-flex items-center gap-2 rounded-full border border-brand-200 bg-white/80 px-3 py-1 text-xs text-brand-700 shadow-sm dark:bg-gray-900/70 dark:border-brand-800 dark:text-brand-300">
<span class="h-2 w-2 rounded-full bg-brand-500"></span> Search results
</p>
<h1 class="mt-4 text-3xl sm:text-4xl font-bold tracking-tight">Everything for <span id="qLabel" class="underline decoration-brand-300"></span></h1>
<p class="mt-3 text-base text-gray-600 dark:text-gray-300">Products, manuals, firmware & tools, knowledge base, and videos across the site.
</p>
<form id="searchForm" class="mt-6">
<label class="relative block">
<input id="q" type="text" placeholder="Type model or serial (e.g. A1, IMUPRO, SN-A1-2025-...)" class="w-full rounded-xl border border-gray-300 bg-white px-4 py-4 pr-28 text-base shadow-sm focus:outline-none focus:ring-2 focus:ring-brand-500 dark:bg-gray-950 dark:border-gray-800" />
<button class="absolute right-2 top-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm hover:border-brand-500 dark:bg-gray-900 dark:border-gray-700" type="submit" aria-label="Search">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35M11 18a7 7 0 100-14 7 7 0 000 14z"/></svg>
</button>
</label>
<p class="mt-2 text-xs text-gray-500">Tip: paste a serial number to autoresolve its model.</p>
</form>
<div id="didYouMean" class="mt-4 hidden text-sm text-gray-600 dark:text-gray-300"></div>
<div class="mt-6 grid grid-cols-2 sm:flex sm:flex-wrap gap-3 text-sm">
<button data-example="A1" class="rounded-xl border border-gray-200 px-3 py-2 hover:border-brand-500 dark:border-gray-800">Try: A1</button>
<button data-example="IMU-PRO" class="rounded-xl border border-gray-200 px-3 py-2 hover:border-brand-500 dark:border-gray-800">Try: IMU-PRO</button>
<button data-example="SN-A1-2025-0008" class="rounded-xl border border-gray-200 px-3 py-2 hover:border-brand-500 dark:border-gray-800">Try: SN-A1-2025-0008</button>
</div>
</div>
<div class="relative">
<div class="relative rounded-2xl border border-gray-200 bg-white p-3 shadow-soft dark:bg-gray-900 dark:border-gray-800">
<div class="aspect-video w-full overflow-hidden rounded-xl bg-gray-100 dark:bg-gray-800">
<img src="https://images.pexels.com/photos/159306/pexels-photo-159306.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600" alt="Construction site — Pexels" class="h-full w-full object-cover"/>
</div>
<div class="mt-3 flex items-center justify-between">
<div class="text-sm text-gray-600 dark:text-gray-300">Search across rugged GPS lineup</div>
<a href="https://www.pexels.com/search/construction%20machinery/" target="_blank" rel="noopener" class="inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm border border-gray-300 hover:border-brand-500 dark:border-gray-700">More on Pexels</a>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Sticky filters -->
<div id="filters" class="sticky top-[64px] z-40 border-y border-gray-200/70 bg-white/80 backdrop-blur supports-[backdrop-filter]:bg-white/60 dark:bg-gray-950/70 dark:border-gray-800">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-3">
<div class="flex flex-wrap items-center gap-2 text-sm">
<label class="inline-flex items-center gap-2"><input type="checkbox" class="accent-brand-600" data-type="product" checked> Products</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" class="accent-brand-600" data-type="doc" checked> Documents</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" class="accent-brand-600" data-type="sw" checked> Software</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" class="accent-brand-600" data-type="kb" checked> Knowledge</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" class="accent-brand-600" data-type="vid" checked> Videos</label>
<span class="h-5 w-px bg-gray-300/60 dark:bg-gray-700"></span>
<select id="langFilter" class="rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 dark:bg-gray-950 dark:border-gray-800"><option value="">All languages</option><option value="en">EN</option><option value="zh">ZH</option><option value="it">IT</option></select>
<select id="platFilter" class="rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 dark:bg-gray-950 dark:border-gray-800"><option value="">All platforms</option><option value="win">Windows</option><option value="mac">macOS</option><option value="linux">Linux</option><option value="all">All</option></select>
<span class="ml-auto text-xs text-gray-500">Employee visibility: <span id="empVis">Public only</span></span>
</div>
</div>
</div>
<!-- Counts & summary -->
<section class="py-6">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div id="summary" class="text-sm text-gray-600 dark:text-gray-300"></div>
</div>
</section>
<!-- Results sections -->
<section class="pb-12">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 space-y-12">
<!-- Products -->
<div id="secProducts" class="space-y-4">
<div class="flex items-end justify-between gap-4">
<h2 class="text-xl sm:text-2xl font-semibold">Products</h2>
<a href="/products/index.html" class="text-sm hover:text-brand-600 dark:hover:text-brand-400">Open catalog</a>
</div>
<div id="gridProducts" class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"></div>
<button id="moreProducts" class="hidden rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700">Show more</button>
</div>
<!-- Documents -->
<div id="secDocs" class="border-t border-gray-200/70 pt-8 dark:border-gray-800">
<div class="flex items-end justify-between gap-4">
<h2 class="text-xl sm:text-2xl font-semibold">Manuals & documents</h2>
<div class="text-xs text-gray-500">Language filter applies</div>
</div>
<div id="listDocs" class="mt-4 grid gap-4"></div>
<button id="moreDocs" class="hidden mt-4 rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700">Show more</button>
</div>
<!-- Software -->
<div id="secSW" class="border-t border-gray-200/70 pt-8 dark:border-gray-800">
<div class="flex items-end justify-between gap-4">
<h2 class="text-xl sm:text-2xl font-semibold">Software & firmware</h2>
<div class="text-xs text-gray-500">Platform filter applies</div>
</div>
<div id="listSW" class="mt-4 grid gap-4"></div>
<button id="moreSW" class="hidden mt-4 rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700">Show more</button>
</div>
<!-- Knowledge -->
<div id="secKB" class="border-t border-gray-200/70 pt-8 dark:border-gray-800">
<h2 class="text-xl sm:text-2xl font-semibold">Knowledge base</h2>
<div id="listKB" class="mt-4 grid gap-4 lg:grid-cols-2"></div>
<button id="moreKB" class="hidden mt-4 rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700">Show more</button>
</div>
<!-- Videos -->
<div id="secVids" class="border-t border-gray-200/70 pt-8 dark:border-gray-800">
<div class="flex items-end justify-between gap-4">
<h2 class="text-xl sm:text-2xl font-semibold">Videos</h2>
<div class="text-xs text-gray-500">Demo player — replace with HLS in production</div>
</div>
<div id="gridVids" class="mt-4 grid gap-6 lg:grid-cols-2"></div>
<button id="moreVids" class="hidden mt-4 rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700">Show more</button>
</div>
<!-- Empty state -->
<div id="empty" class="hidden rounded-2xl border border-dashed border-gray-300 p-8 text-center text-gray-600 dark:border-gray-700 dark:text-gray-300">
No results found. Try a different term, or browse the <a class="underline" href="/products/index.html">catalog</a> or <a class="underline" href="/support/index.html">support</a>.
</div>
</div>
</section>
<!-- Footer -->
<footer class="mt-12 border-t border-gray-200/70 dark:border-gray-800">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-10">
<div class="grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
<div>
<div class="flex items-center gap-2">
<span class="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-brand-600 text-white shadow-soft">L</span>
<span class="font-semibold">Leitan GPS</span>
</div>
<p class="mt-3 text-sm text-gray-600 dark:text-gray-300">Search across products and resources.</p>
</div>
<div>
<div class="font-semibold">Product</div>
<ul class="mt-3 space-y-2 text-sm text-gray-600 dark:text-gray-300">
<li><a href="/support/index.html" class="hover:underline">Support</a></li>
<li><a href="/products/index.html" class="hover:underline">Catalog</a></li>
</ul>
</div>
<div>
<div class="font-semibold">Resources</div>
<ul class="mt-3 space-y-2 text-sm text-gray-600 dark:text-gray-300">
<li><a href="#" class="hover:underline">Privacy policy</a></li>
<li><a href="#" class="hover:underline">Cookie preferences</a></li>
</ul>
</div>
<div>
<div class="font-semibold">Contact</div>
<ul class="mt-3 space-y-2 text-sm text-gray-600 dark:text-gray-300">
<li>support@example.com</li>
<li>MonFri, 09:0018:00</li>
<li>Italy / Singapore</li>
</ul>
</div>
</div>
<div class="mt-8 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-gray-500">
<div>© 2025 Leitan Tech. All rights reserved.</div>
<a href="#top" class="inline-flex items-center gap-1 hover:text-brand-600 dark:hover:text-brand-400">Back to top <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 15l7-7 7 7"/></svg></a>
</div>
</div>
</footer>
<!-- Scripts -->
<script>
// Theme init
(function(){ const s=localStorage.getItem('theme'); const d=window.matchMedia('(prefers-color-scheme: dark)').matches; if (s==='dark' || (!s && d)) document.documentElement.classList.add('dark'); })();
const themeBtn=document.getElementById('themeToggle'); const sun=document.getElementById('iconSun'); const moon=document.getElementById('iconMoon');
function syncIcons(){ const d=document.documentElement.classList.contains('dark'); sun?.classList.toggle('hidden',!d); moon?.classList.toggle('hidden',d); }
themeBtn?.addEventListener('click',()=>{ document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark')?'dark':'light'); syncIcons(); }); syncIcons();
// Employee mode
const isEmp = !!localStorage.getItem('employee');
const empBadge = document.getElementById('empBadge');
empBadge.textContent = isEmp ? 'Employee mode' : 'Visitor mode';
empBadge.classList.remove('hidden');
document.getElementById('empVis').textContent = isEmp ? 'Public + Internal' : 'Public only';
// Datasets (reuse demo data from other pages)
const PRODUCTS=[
{id:'A1', model:'A1', category:'Positioning unit', snPrefixes:['SN-A1-','A1-2025-'], desc:'MultiGNSS receiver for construction machinery.', specs:['RTK cmlevel','CAN/RS485','IP67'], thumb:'https://images.pexels.com/photos/280140/pexels-photo-280140.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600'},
{id:'IMU-PRO', model:'IMU-PRO', category:'Sensor (IMU)', snPrefixes:['SN-IMU-','IMU-2025-'], desc:'6axis industrial IMU for machine guidance.', specs:['Low drift','HighG tolerant','IP65'], thumb:'https://images.pexels.com/photos/14452156/pexels-photo-14452156.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600'},
{id:'AX-5', model:'AX-5', category:'Antenna', snPrefixes:['SN-AX5-'], desc:'GNSS L1/L2 antenna with magnetic mount.', specs:['L1/L2 bands','Magnetic base','IP66'], thumb:'https://images.pexels.com/photos/159306/pexels-photo-159306.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600'}
];
const DOCS=[
{id:'doc-001', productId:'A1', title:'A1 Wiring & Installation', type:'manual', version:'v1.8', lang:'en', size:'2.3MB', updatedAt:'2025-07-08', checksum:'SHA256-…d9c', url:'#', visibility:'public'},
{id:'doc-002', productId:'A1', title:'A1 Service Manual', type:'service', version:'v1.7', lang:'en', size:'4.7MB', updatedAt:'2025-07-22', checksum:'SHA256-…9ab', url:'#', visibility:'internal'},
{id:'doc-010', productId:'IMU-PRO', title:'IMUPRO Datasheet', type:'datasheet', version:'v3.0', lang:'en', size:'1.1MB', updatedAt:'2025-06-30', checksum:'SHA256-…ab2', url:'#', visibility:'public'}
];
const SW=[
{id:'sw-101', productId:'A1', title:'A1 Config Tool', kind:'software', version:'v2.2.0', platform:'win', size:'18.4MB', updatedAt:'2025-06-20', checksum:'SHA256-…6a5', notesUrl:'#', compat:'A1 hw v2.x+', url:'#', visibility:'public', risk:false},
{id:'sw-102', productId:'A1', title:'A1 Firmware', kind:'firmware', version:'v2.3.1', platform:'all', size:'21.0MB', updatedAt:'2025-08-02', checksum:'MD5-…1c3', notesUrl:'#', compat:'A1 hw v2.x+', url:'#', visibility:'internal', risk:true},
{id:'sw-210', productId:'IMU-PRO', title:'IMUPRO Tool', kind:'software', version:'v1.4.2', platform:'win', size:'11.2MB', updatedAt:'2025-07-10', checksum:'SHA256-…9ee', notesUrl:'#', compat:'IMUPRO all', url:'#', visibility:'public', risk:false}
];
const KB=[
{id:'kb-301', productId:'A1', title:'Position drift under vibration — checklist', summary:'Mounting, grounding, EMI, power ripple…', tags:['Troubleshooting','Installation'], url:'#', visibility:'internal'},
{id:'kb-302', productId:'A1', title:'Quick start — first fix in 60s', summary:'A minimal path to first RTK fix.', tags:['Quickstart'], url:'#', visibility:'public'}
];
const VIDS=[
{id:'vid-501', productId:'A1', title:'A1 quick install (2:13)', duration:'02:13', poster:'https://images.pexels.com/photos/14452156/pexels-photo-14452156.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600', sources:[{src:'/media/a1-install-720.mp4',label:'720p'}], visibility:'public'},
{id:'vid-502', productId:'A1', title:'Internal: wiring pitfalls (3:40)', duration:'03:40', poster:'https://images.pexels.com/photos/159306/pexels-photo-159306.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600', sources:[{src:'/media/a1-internal-720.mp4',label:'720p'}], visibility:'internal'}
];
// Helpers
function escReg(s){ return s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); }
function hi(text,term){ if(!term) return text; try{ const re=new RegExp(`(${escReg(term)})`,'ig'); return text.replace(re,'<mark>$1</mark>'); }catch{ return text; } }
function badgeInternal(v){ return v==='internal'?'<span class="inline-flex items-center gap-1 rounded bg-gray-100 px-2 py-1 text-[11px] text-gray-600 dark:bg-gray-800 dark:text-gray-300">\uD83D\uDD12 Internal</span>':''; }
function lockCls(v){ return v==='internal' && !isEmp ? 'opacity-40 pointer-events-none' : ''; }
// Resolve serial -> model
function resolveBySerial(input){ const s=input.toUpperCase(); for(const p of PRODUCTS){ if((p.snPrefixes||[]).some(pre=>s.startsWith(pre))) return p; } return null; }
// DOM refs
const qInput=document.getElementById('q');
const qLabel=document.getElementById('qLabel');
const didYou=document.getElementById('didYouMean');
const summary=document.getElementById('summary');
const params=new URLSearchParams(location.search); const initial=params.get('q')||''; if(initial){ qInput.value=initial; }
// State
let PAGE={product:6, doc:6, sw:6, kb:6, vid:4};
function updateSummary(q,counts){
const total=counts.product+counts.doc+counts.sw+counts.kb+counts.vid;
summary.innerHTML = `${total} results · Products ${counts.product} · Documents ${counts.doc} · Software ${counts.sw} · KB ${counts.kb} · Videos ${counts.vid}`;
}
// Renderers
function renderProducts(items,q){ const grid=document.getElementById('gridProducts'); const more=document.getElementById('moreProducts'); const page=PAGE.product; const list=items.slice(0,page);
grid.innerHTML=list.map(p=>`
<article class='group relative rounded-2xl border border-gray-200 bg-white shadow-soft hover:shadow-lg transition-shadow dark:bg-gray-900 dark:border-gray-800'>
<div class='relative overflow-hidden rounded-t-2xl'>
<div class='aspect-[16/9]'><img src='${p.thumb}' alt='${p.model}${p.category}' class='h-full w-full object-cover transition-transform duration-500 group-hover:scale-105'/></div>
</div>
<div class='p-5'>
<div class='text-xs text-gray-500'>${hi(p.category,q)}</div>
<h3 class='mt-0.5 text-lg font-semibold tracking-tight'>${hi(p.model,q)}</h3>
<p class='mt-1 text-sm text-gray-600 dark:text-gray-300 line-clamp-2'>${hi(p.desc,q)}</p>
<div class='mt-4 flex items-center gap-2'>
<a class='rounded-lg bg-brand-600 px-3 py-1.5 text-sm text-white' href='/p/${p.model.toLowerCase()}.html?model=${encodeURIComponent(p.model)}'>View product</a>
<a class='rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700' href='/support/index.html?q=${encodeURIComponent(p.model)}'>Downloads</a>
</div>
</div>
</article>`).join('');
more.classList.toggle('hidden',items.length<=page);
more.onclick=()=>{ PAGE.product+=6; renderProducts(items,q); };
}
function renderDocs(items,q){ const list=document.getElementById('listDocs'); const more=document.getElementById('moreDocs'); const page=PAGE.doc; const arr=items.slice(0,page);
list.innerHTML=arr.map(d=>`
<div class='flex flex-col sm:flex-row items-start sm:items-center gap-4 rounded-2xl border border-gray-200 bg-white p-4 shadow-soft dark:bg-gray-900 dark:border-gray-800 ${lockCls(d.visibility)}'>
<div class='flex min-w-0 flex-1 items-center gap-4'>
<div class='flex h-12 w-12 items-center justify-center rounded-xl bg-brand-50 text-brand-700 dark:bg-brand-900/30 dark:text-brand-300'>
<svg class='h-6 w-6' fill='none' stroke='currentColor' stroke-width='1.5' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' d='M12 6v6m0 0l-3-3m3 3l3-3M4 18h16'/></svg>
</div>
<div class='min-w-0'>
<div class='font-medium truncate'>${hi(d.title,q)} (${(d.version||'').toUpperCase()} · ${(d.lang||'').toUpperCase()})</div>
<div class='text-xs text-gray-500'>${d.type} · ${d.size||'—'} · ${d.updatedAt||'—'} · ${d.checksum||''} · <span class='inline-flex items-center gap-1 rounded-full bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-gray-800 dark:text-gray-300'>${d.productId}</span></div>
</div>
</div>
<div class='flex shrink-0 items-center gap-3'>
${badgeInternal(d.visibility)}
<a href='${d.url||'#'}' class='inline-flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:border-brand-500 dark:border-gray-700'>Download</a>
<a href='/support/index.html?q=${encodeURIComponent(d.productId)}' class='inline-flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:border-brand-500 dark:border-gray-700'>More for ${d.productId}</a>
</div>
</div>`).join('');
more.classList.toggle('hidden',items.length<=page);
more.onclick=()=>{ PAGE.doc+=6; renderDocs(items,q); };
}
function renderSW(items,q){ const list=document.getElementById('listSW'); const more=document.getElementById('moreSW'); const page=PAGE.sw; const arr=items.slice(0,page);
list.innerHTML=arr.map(s=>`
<div class='flex flex-col sm:flex-row items-start sm:items-center gap-4 rounded-2xl border border-gray-200 bg-white p-4 shadow-soft dark:bg-gray-900 dark:border-gray-800 ${lockCls(s.visibility)}'>
<div class='flex min-w-0 flex-1 items-center gap-4'>
<div class='flex h-12 w-12 items-center justify-center rounded-xl ${s.kind==='firmware'?'bg-amber-50 text-amber-700 dark:bg-amber-900/20 dark:text-amber-300':'bg-brand-50 text-brand-700 dark:bg-brand-900/30 dark:text-brand-300'}'>
<svg class='h-6 w-6' fill='none' stroke='currentColor' stroke-width='1.5' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' d='M4 4h16v12H4z'/><path stroke-linecap='round' stroke-linejoin='round' d='M4 20h16'/></svg>
</div>
<div class='min-w-0'>
<div class='font-medium truncate'>${hi(s.title,q)} (${s.version}) — ${s.kind}</div>
<div class='text-xs text-gray-500'>${(s.platform||'').toUpperCase()} · ${s.size||'—'} · ${s.updatedAt||'—'} · ${s.checksum||''} · Compat: ${s.compat||'—'} · <span class='inline-flex items-center gap-1 rounded-full bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-gray-800 dark:text-gray-300'>${s.productId}</span></div>
</div>
</div>
<div class='flex shrink-0 items-center gap-3'>
${badgeInternal(s.visibility)}
<a href='${s.notesUrl||'#'}' class='inline-flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:border-brand-500 dark:border-gray-700'>Release notes</a>
<button class='inline-flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:border-brand-500 dark:border-gray-700' onclick='${s.kind==='firmware'?'openRisk()':'alert("Download started (demo)")'}'>Download</button>
</div>
</div>`).join('');
more.classList.toggle('hidden',items.length<=page);
more.onclick=()=>{ PAGE.sw+=6; renderSW(items,q); };
}
function renderKB(items,q){ const list=document.getElementById('listKB'); const more=document.getElementById('moreKB'); const page=PAGE.kb; const arr=items.slice(0,page);
list.innerHTML=arr.map(k=>`
<article class='rounded-2xl border border-gray-200 bg-white p-6 shadow-soft dark:bg-gray-900 dark:border-gray-800 ${lockCls(k.visibility)}'>
<div class='text-xs text-gray-500'>${(k.tags||[]).join(' · ')}</div>
<h3 class='mt-1 font-semibold'>${hi(k.title,q)}</h3>
<p class='mt-1 text-sm text-gray-600 dark:text-gray-300'>${hi(k.summary||'',q)}</p>
<div class='mt-3 flex items-center gap-3'>${badgeInternal(k.visibility)}<a href='${k.url||'#'}' class='inline-flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:border-brand-500 dark:border-gray-700'>View</a><a href='/support/index.html?q=${encodeURIComponent(k.productId)}' class='inline-flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:border-brand-500 dark:border-gray-700'>More for ${k.productId}</a></div>
</article>`).join('');
more.classList.toggle('hidden',items.length<=page);
more.onclick=()=>{ PAGE.kb+=6; renderKB(items,q); };
}
function renderVids(items){ const grid=document.getElementById('gridVids'); const more=document.getElementById('moreVids'); const page=PAGE.vid; const arr=items.slice(0,page);
grid.innerHTML=arr.map(v=>`
<div class='rounded-2xl border border-gray-200 bg-white p-3 shadow-soft dark:bg-gray-900 dark:border-gray-800 ${lockCls(v.visibility)}'>
<div class='aspect-video w-full overflow-hidden rounded-xl bg-gray-100 dark:bg-gray-800'>
<video controls preload='metadata' poster='${v.poster||''}' class='h-full w-full'>${(v.sources||[]).map(s=>`<source src='${s.src}' type='video/mp4'/>`).join('')}</video>
</div>
<div class='mt-3 flex items-center justify-between'>
<div class='text-sm text-gray-600 dark:text-gray-300'>${v.title||''}</div>
${badgeInternal(v.visibility)}
</div>
</div>`).join('');
more.classList.toggle('hidden',items.length<=page);
more.onclick=()=>{ PAGE.vid+=4; renderVids(items); };
}
// Search logic
function runSearch(){
const q=(qInput.value||'').trim();
qLabel.textContent=q||'—';
didYou.classList.add('hidden');
// serial -> product hint
const serialHit = resolveBySerial(q);
if(!serialHit && q && q.length>=2){
// suggest closest model by prefix
const maybe = PRODUCTS.find(p=>p.model.toLowerCase().startsWith(q.toLowerCase()));
if(maybe){ didYou.innerHTML=`Did you mean <a class='underline' href='?q=${encodeURIComponent(maybe.model)}'>${maybe.model}</a>?`; didYou.classList.remove('hidden'); }
}
// Filters
const chosenTypes=[...document.querySelectorAll('#filters input[type=checkbox][data-type]:checked')].map(el=>el.getAttribute('data-type'));
const lang=document.getElementById('langFilter').value;
const plat=document.getElementById('platFilter').value;
// Products
let products = PRODUCTS.filter(p=>{
if(!q) return true; const s=q.toLowerCase();
return p.model.toLowerCase().includes(s) || p.desc.toLowerCase().includes(s) || (p.snPrefixes||[]).some(pre=>s.startsWith(pre.toLowerCase()));
});
if(serialHit && !products.some(x=>x.id===serialHit.id)) products=[serialHit,...products];
// Docs/SW/KB/Vids — match on title/summary and product
function matchText(x){ if(!q) return true; const s=q.toLowerCase(); return (x.title||'').toLowerCase().includes(s) || (x.summary||'').toLowerCase().includes(s) || (x.productId||'').toLowerCase().includes(s); }
function langOk(x){ return !lang || (x.lang||'').toLowerCase()===lang; }
function platOk(x){ return !plat || (x.platform===plat || x.platform==='all'); }
let docs = DOCS.filter(x=>matchText(x) && langOk(x));
let sw = SW.filter(x=>matchText(x) && platOk(x));
let kbs = KB.filter(x=>matchText(x));
let vids = VIDS.filter(x=>matchText(x));
// sort resources by recency
const byDate=(a,b)=>new Date(b.updatedAt||'1970-01-01')-new Date(a.updatedAt||'1970-01-01');
docs.sort(byDate); sw.sort(byDate);
// Counts & empty state
const counts={ product:products.length, doc:docs.length, sw:sw.length, kb:kbs.length, vid:vids.length };
updateSummary(q,counts);
const nothing = counts.product+counts.doc+counts.sw+counts.kb+counts.vid===0;
document.getElementById('empty').classList.toggle('hidden',!nothing);
// Render by type toggles
document.getElementById('secProducts').classList.toggle('hidden',!chosenTypes.includes('product'));
document.getElementById('secDocs').classList.toggle('hidden',!chosenTypes.includes('doc'));
document.getElementById('secSW').classList.toggle('hidden',!chosenTypes.includes('sw'));
document.getElementById('secKB').classList.toggle('hidden',!chosenTypes.includes('kb'));
document.getElementById('secVids').classList.toggle('hidden',!chosenTypes.includes('vid'));
// Render
PAGE={product:6, doc:6, sw:6, kb:6, vid:4};
renderProducts(products,q);
renderDocs(docs,q);
renderSW(sw,q);
renderKB(kbs,q);
renderVids(vids,q);
}
// Events
document.getElementById('searchForm').addEventListener('submit',e=>{ e.preventDefault(); runSearch(); history.replaceState(null,'',`?q=${encodeURIComponent(qInput.value||'')}`); });
document.querySelectorAll('[data-example]').forEach(btn=>btn.addEventListener('click',()=>{ qInput.value=btn.getAttribute('data-example'); runSearch(); history.replaceState(null,'',`?q=${encodeURIComponent(qInput.value||'')}`); }));
document.querySelectorAll('#filters input[type=checkbox][data-type]').forEach(el=>el.addEventListener('change',runSearch));
document.getElementById('langFilter').addEventListener('change',runSearch);
document.getElementById('platFilter').addEventListener('change',runSearch);
// Firmware risk modal (shared behaviour)
function toggleRisk(v){ document.getElementById('risk')?.classList.toggle('hidden',!v); }
window.openRisk=function(){ toggleRisk(true); }
// Initial
runSearch();
</script>
<!-- Risk modal host (optional, no content until needed) -->
<div id="risk" class="fixed inset-0 z-[70] hidden">
<div class="absolute inset-0 bg-black/40" onclick="document.getElementById('risk').classList.add('hidden')"></div>
<div class="absolute left-1/2 top-1/2 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-2xl border border-gray-200 bg-white p-6 shadow-2xl dark:bg-gray-900 dark:border-gray-800">
<h4 class="font-semibold">Firmware download confirmation</h4>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-300">Confirm you have checked compatibility and rollback instructions. Incorrect firmware may brick the device.</p>
<div class="mt-4 flex justify-end gap-2">
<button class="rounded-lg border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-700" onclick="document.getElementById('risk').classList.add('hidden')">Close</button>
<button class="rounded-lg bg-brand-600 px-3 py-1.5 text-sm text-white" onclick="alert('Download started (demo)'); document.getElementById('risk').classList.add('hidden')">Download</button>
</div>
</div>
</div>
</body>
</html>