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

252 lines
16 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>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>