High-conversion landing · real client, live
Kavita Drones

Problem
An agricultural-drone reseller needed to showcase products and capture quotes without relying on social media.
Solution
Fast, mobile-first landing with catalog + dynamic quote sent straight to WhatsApp. Delivered and live.
How it was built
Zero framework, on purpose: semantic HTML5, plain CSS and vanilla JavaScript, with no build step. You just open index.html in the browser. It's about 7,200 lines split across 3 files (script.js at 1,735 lines, styles.css at 4,239), a decision the README itself documents as a "self-contained landing", kept separate from Kavita's core system. Hosting today is Cloudflare Workers via wrangler.jsonc serving the files as static assets, with a leftover vercel.json that only permanently redirects www.kavita.com.br to kavita.com.br. It's the trace of a provider migration done without breaking the old domain.
The equipment catalog (26 items across 5 categories: batteries, chargers and power, tank mixers, sprayer pumps and other accessories) lives in an ACCESSORIES array in script.js, but it's treated as governed data, not loose content. Every item carries a status field, which can be confirmado, pendente-imagem, pendente-especificacao or pendente-compatibilidade, and the project's rule, documented in its docs, is explicit: "nothing is invented". Specs without a confirmed source (an official DJI manual or an Agrobox technical page) stay flagged as pending instead of getting filled in with a guess. Category and drone-compatibility filters (T25P/T70P/T100) combine with AND logic, so an item only shows up when it matches both criteria at once.
The quote builder works like a mini-cart with no store behind it. Visitors add drones, equipment and services to a budget that persists in localStorage across sessions (with a silent fallback if the browser blocks storage) and see a live item counter. On lead-form submit, the site assembles a structured message, split into "Drones", "Equipment and accessories" and "Services" blocks, then redirects via wa.me. The destination number changes per unit: a REPRESENTATIVES object maps 4 branches (Mateus/HQ, Itaperuna-RJ, Manhuaçu-MG, Cachoeiro de Itapemirim-ES) to 4 different WhatsApp numbers, falling back to a default if the visitor picks none.
Several performance and accessibility details that usually come from a framework were hand-built. The hero uses preload with fetchpriority="high" for the critical image and preconnect for Google Fonts. The hero background video pauses via IntersectionObserver when it leaves the viewport, saving CPU and battery, and won't even play if prefers-reduced-motion is on. The animated counters (DJI stat numbers) use requestAnimationFrame with a hand-written easeOutCubic easing, computed straight from the HTML's own text, so they work as pure progressive enhancement: with no JS, the final number is already correct. And the light/dark theme respects the system preference (matchMedia) on first visit, then persists the user's choice in localStorage.
Catalog as governed data
26 items with per-field confirmed or pending status. No specification is ever invented.
Persistent multi-item quote
Drones + equipment + services cart in localStorage, turned into a structured WhatsApp message.
Regional unit routing
4 branches (MG/ES/RJ states) mapped to 4 different WhatsApp numbers, with a default fallback.
Zero framework, hand-built performance
Vanilla JS with IntersectionObserver, fetchpriority and prefers-reduced-motion, with no build step at all.
More screens
Stack
- HTML
- CSS
- JavaScript
- Vercel


