473 lines
35 KiB
HTML
473 lines
35 KiB
HTML
|
<!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, IMU‑PRO, 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 auto‑resolve 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>Mon–Fri, 09:00–18: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:'Multi‑GNSS receiver for construction machinery.', specs:['RTK cm‑level','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:'6‑axis industrial IMU for machine guidance.', specs:['Low drift','High‑G 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:'IMU‑PRO 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:'IMU‑PRO Tool', kind:'software', version:'v1.4.2', platform:'win', size:'11.2MB', updatedAt:'2025-07-10', checksum:'SHA256-…9ee', notesUrl:'#', compat:'IMU‑PRO 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>
|