252 lines
16 KiB
HTML
Raw Permalink Normal View History

2025-08-27 13:34:10 +08:00
<!doctype html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Product Catalog · Leitan GPS</title>
<meta name="description" content="Browse construction machinery GPS products by category. Quick specs, search, and links to support & downloads." />
<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; }
</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" aria-current="page" class="text-brand-700 dark:text-brand-300 font-medium">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">
<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>
<!-- Hero / Controls -->
<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-12 lg:py-16 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> Product Catalog
</p>
<h1 class="mt-4 text-3xl sm:text-4xl font-bold tracking-tight">Rugged GPS hardware for machinery</h1>
<p class="mt-3 text-base text-gray-600 dark:text-gray-300 max-w-2xl">Browse by category, search by model, and jump straight to downloads.
</p>
<div class="mt-6 grid gap-3 sm:grid-cols-[1fr_auto]">
<label class="relative block">
<input id="q" type="text" placeholder="Search model (e.g. A1, IMU-PRO, AX-5)" 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 id="btnSearch" 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="button" 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>
<div class="flex items-center gap-2 text-sm">
<label class="sr-only" for="sort">Sort</label>
<select id="sort" class="rounded-xl border border-gray-300 bg-white px-3 py-3 dark:bg-gray-950 dark:border-gray-800">
<option value="az">Sort: A → Z</option>
<option value="za">Sort: Z → A</option>
<option value="new">Sort: Newest</option>
</select>
</div>
</div>
<div class="mt-4 flex flex-wrap items-center gap-2 text-sm">
<button class="cat active rounded-full border border-brand-500 text-brand-700 px-3 py-1.5 dark:text-brand-300">All</button>
<button class="cat rounded-full border border-gray-300 px-3 py-1.5 dark:border-gray-700" data-cat="Positioning unit">Positioning units</button>
<button class="cat rounded-full border border-gray-300 px-3 py-1.5 dark:border-gray-700" data-cat="Sensor (IMU)">Sensors</button>
<button class="cat rounded-full border border-gray-300 px-3 py-1.5 dark:border-gray-700" data-cat="Antenna">Antennas</button>
<button id="reset" class="ml-1 rounded-full border border-gray-300 px-3 py-1.5 dark:border-gray-700">Reset</button>
<span id="count" class="ml-auto text-xs text-gray-500">0 items</span>
</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">Precision hardware for harsh environments</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>
<!-- Catalog Grid -->
<section class="pb-12">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div id="grid" class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"></div>
<div id="empty" class="hidden mt-6 rounded-2xl border border-gray-200 bg-white p-6 text-sm text-gray-600 dark:bg-gray-900 dark:border-gray-800 dark:text-gray-300">
No products match your filters. Try clearing search or switching category.
</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">Browse products and jump to downloads.
</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="/employee/index.html" class="hover:underline">Employee portal</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();
// Demo product dataset
const PRODUCTS=[
{id:'A1', model:'A1', category:'Positioning unit', thumb:'https://images.pexels.com/photos/280140/pexels-photo-280140.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600', desc:'MultiGNSS receiver for construction machinery.', specs:['RTK cmlevel','CAN/RS485','IP67'], new:true},
{id:'IMU-PRO', model:'IMU-PRO', category:'Sensor (IMU)', thumb:'https://images.pexels.com/photos/14452156/pexels-photo-14452156.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600', desc:'6axis industrial IMU for machine guidance.', specs:['Low drift','HighG tolerant','IP65']},
{id:'AX-5', model:'AX-5', category:'Antenna', thumb:'https://images.pexels.com/photos/159306/pexels-photo-159306.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=1600', desc:'GNSS L1/L2 antenna with magnetic mount.', specs:['L1/L2 bands','Magnetic base','IP66']}
];
const grid=document.getElementById('grid');
const empty=document.getElementById('empty');
const count=document.getElementById('count');
const q=document.getElementById('q');
const sortSel=document.getElementById('sort');
let activeCat='All';
function card(p){
const spec=p.specs.map(s=>`<li class='truncate'>${s}</li>`).join('');
const badge=p.new?`<span class='absolute left-3 top-3 rounded-full bg-brand-600/90 text-white text-[11px] px-2 py-0.5'>NEW</span>`:'';
return `
<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'>
${badge}
<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'>${p.category}</div>
<h3 class='mt-0.5 text-lg font-semibold tracking-tight'>${p.model}</h3>
<p class='mt-1 text-sm text-gray-600 dark:text-gray-300 line-clamp-2'>${p.desc}</p>
<ul class='mt-3 grid grid-cols-3 gap-2 text-xs text-gray-600 dark:text-gray-300'>${spec}</ul>
<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>`;
}
function apply(){
const term=(q.value||'').trim().toLowerCase();
let items=PRODUCTS.filter(p=> activeCat==='All' || p.category===activeCat);
if(term) items=items.filter(p=> p.model.toLowerCase().includes(term) || p.desc.toLowerCase().includes(term));
// sort
const s=sortSel.value;
items=items.slice();
if(s==='az') items.sort((a,b)=>a.model.localeCompare(b.model));
if(s==='za') items.sort((a,b)=>b.model.localeCompare(a.model));
if(s==='new') items.sort((a,b)=> (b.new?1:0) - (a.new?1:0) || a.model.localeCompare(b.model));
grid.innerHTML=items.map(card).join('');
count.textContent=`${items.length} of ${PRODUCTS.length} items`;
empty.classList.toggle('hidden', items.length>0);
}
// Category chips
document.querySelectorAll('.cat').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('.cat').forEach(b=>b.classList.remove('active','border-brand-500','text-brand-700','dark:text-brand-300'));
btn.classList.add('active','border-brand-500','text-brand-700','dark:text-brand-300');
activeCat=btn.getAttribute('data-cat')||'All';
apply();
});
});
document.getElementById('reset').addEventListener('click',()=>{ q.value=''; activeCat='All'; document.querySelectorAll('.cat')[0].click(); sortSel.value='az'; apply(); });
q.addEventListener('keydown',e=>{ if(e.key==='Enter'){ apply(); }});
document.getElementById('btnSearch').addEventListener('click',apply);
sortSel.addEventListener('change',apply);
// Init with query param
const params=new URLSearchParams(location.search); const pre=params.get('q'); if(pre){ q.value=pre; }
apply();
</script>
</body>
</html>