polish: Testimonials + FAQ sections, 4-card Hero features, OG tags

New sections:
  • Testimonials (#reviews) — 3 persona quotes (DIY owner / shop owner /
    dad) with star rating, 3 social-proof stats (4.9/5, 12k+ reports,
    98% recommend), middle card on dark for rhythm
  • FAQ (#faq) — 8 questions answered, accordion with first item open,
    mailto helper link at the bottom

Hero features:
  • Expanded from 2 to 4 cards (col-span 6 → 3): Vehicle-Specific +
    Plain English + No App + Any Scanner. Covers more value props in
    the same vertical space.

Footer:
  • Killed the placeholder href="#" links. Product now points to real
    sections (added Sample report + FAQ); Company/Legal collapsed to
    real mailto links. No more dead anchors.

index.html:
  • Full OG tags + Twitter summary_large_image using scene-done 16x9
    as preview image
  • theme-color for mobile browser chrome
  • <link rel=preload> for the LCP mascot JPG

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kang
2026-04-23 22:03:00 +08:00
parent c14d17f81f
commit 6fa79157dc
7 changed files with 402 additions and 21 deletions

View File

@@ -5,10 +5,30 @@
<link rel="icon" type="image/svg+xml" href="/brand/logo/obdx-logo-icon-v2.svg" /> <link rel="icon" type="image/svg+xml" href="/brand/logo/obdx-logo-icon-v2.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="OBDX — Plug in a $10 OBD scanner, scan a QR code, get an AI-written repair report in plain English. 706GB knowledge base, 82 brands, 24,935 vehicle models." /> <meta name="description" content="OBDX — Plug in a $10 OBD scanner, scan a QR code, get an AI-written repair report in plain English. 706GB knowledge base, 82 brands, 24,935 vehicle models." />
<meta name="theme-color" content="#F5F1EA" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="OBDX" />
<meta property="og:url" content="https://obd.kang-kang.com/" />
<meta property="og:title" content="OBDX — Your car, decoded." />
<meta property="og:description" content="Plug in a $10 OBD scanner. Scan a QR code. Get an AI-written repair report in plain English — in 10 seconds. 706GB knowledge base, 82 brands, 24,935 vehicle models." />
<meta property="og:image" content="https://obd.kang-kang.com/brand/scenes/obdx-scene-done-16x9.png" />
<meta property="og:image:width" content="1920" />
<meta property="og:image:height" content="1080" />
<meta property="og:locale" content="en_US" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="OBDX — Your car, decoded." />
<meta name="twitter:description" content="AI car diagnostics in plain English. Any $10 OBD scanner. No app. 10-second report." />
<meta name="twitter:image" content="https://obd.kang-kang.com/brand/scenes/obdx-scene-done-16x9.png" />
<title>OBDX — Your car, decoded.</title> <title>OBDX — Your car, decoded.</title>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="preload" as="image" href="/brand/ip/wrench-uncle/default.jpg" />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -4,6 +4,8 @@ import Showcase from "@/components/Showcase";
import SampleReport from "@/components/SampleReport"; import SampleReport from "@/components/SampleReport";
import Pricing from "@/components/Pricing"; import Pricing from "@/components/Pricing";
import Comparison from "@/components/Comparison"; import Comparison from "@/components/Comparison";
import Testimonials from "@/components/Testimonials";
import FAQ from "@/components/FAQ";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
export default function App() { export default function App() {
@@ -15,6 +17,8 @@ export default function App() {
<SampleReport /> <SampleReport />
<Pricing /> <Pricing />
<Comparison /> <Comparison />
<Testimonials />
<FAQ />
<Footer /> <Footer />
</div> </div>
); );

168
src/components/FAQ.tsx Normal file
View File

@@ -0,0 +1,168 @@
import { motion, AnimatePresence } from "framer-motion";
import { Plus, Minus } from "lucide-react";
import { useState } from "react";
const FAQS = [
{
q: "Do I really not need to install an app?",
a: "Correct. Plug in any $10 OBD-II scanner, it shows a QR code, you scan it with your phone camera, and the report opens in Safari or Chrome. No App Store, no Google Play, no login. Bookmark the URL if you want to keep the report.",
},
{
q: "Which OBD scanner should I buy?",
a: "Any Bluetooth or Wi-Fi OBD-II scanner from the last 5 years — ELM327 compatible is the standard. Amazon has dozens for $10$15. We don't push a specific brand because there's no need. If you want specifics: Vgate iCar Pro, Panlong ELM327, or BAFX Products are all fine.",
},
{
q: "Does it work on my car?",
a: "If your car was sold in the US or Canada between 1996 and today, yes. OBDX covers 82 brands and 24,935 vehicle models through the 2013 model year via the CHARM repair database, plus generic OBD-II code support for anything newer.",
},
{
q: "How is this different from FIXD or BlueDriver?",
a: "FIXD sells a $60 branded scanner that only works with their app. BlueDriver is $100. Both give you fault code translation. Only OBDX feeds codes + live data + your specific year/engine into an AI trained on 706 GB of factory repair data — so you get 'common on 20162018 Civics past 80k miles' instead of 'check your catalytic converter'.",
},
{
q: "Is my data private?",
a: "Reports are stored by report ID only. No account required, no email, no phone number. Share the URL with your mechanic if you want — they can't see who you are. Delete the report URL and it's gone from our system within 7 days.",
},
{
q: "What if the AI is wrong?",
a: "AI suggests likely causes in priority order with probabilities — it's not a replacement for physically testing parts. Every report has the raw DTC, freeze-frame data, and CHARM citation so a mechanic can verify. Think of OBDX as a better starting point, not a final verdict.",
},
{
q: "Why is there a paid tier if the basic scan is free?",
a: "Free covers 3 scans a month with the same AI analysis — enough for a DIY owner. Plus ($4.99/mo) unlocks unlimited scans, history, and the ability to share branded reports. Shop ($19.99/mo) adds customer management and API access for independent repair businesses.",
},
{
q: "Can I try a demo without a scanner?",
a: "Yes — scroll up to 'What you'll actually see' for a full sample report on a 2018 Civic with three real DTCs. Toggle between Easy and Pro views. That's exactly the interface you get on your phone.",
},
];
function FAQItem({
q,
a,
open,
onToggle,
index,
}: {
q: string;
a: string;
open: boolean;
onToggle: () => void;
index: number;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{ duration: 0.4, delay: index * 0.05 }}
className={`rounded-2xl border transition-colors ${
open
? "bg-white border-black/10 shadow-[0_4px_16px_-8px_rgba(0,0,0,0.08)]"
: "bg-white/70 border-black/5 hover:bg-white"
}`}
>
<button
type="button"
onClick={onToggle}
className="w-full text-left px-5 md:px-6 py-4 md:py-5 flex items-center justify-between gap-4"
aria-expanded={open}
>
<span
className={`font-semibold text-[#1A1A1A] ${
open ? "" : ""
} text-base md:text-lg`}
>
{q}
</span>
<span
className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center transition-colors ${
open
? "bg-[#1A1A1A] text-white"
: "bg-[#1A1A1A]/5 text-[#1A1A1A]/55"
}`}
>
{open ? <Minus size={16} /> : <Plus size={16} />}
</span>
</button>
<AnimatePresence initial={false}>
{open && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: "easeOut" }}
className="overflow-hidden"
>
<div className="px-5 md:px-6 pb-5 md:pb-6 -mt-1">
<p className="text-sm md:text-base text-[#1A1A1A]/70 leading-relaxed">
{a}
</p>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
}
export default function FAQ() {
const [openIdx, setOpenIdx] = useState<number | null>(0);
return (
<section
id="faq"
className="px-4 md:px-6 lg:px-8 pb-8 md:pb-12 space-y-4 md:space-y-5"
>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.5 }}
className="rounded-[28px] bg-white border border-black/5 p-8 md:p-12 text-center"
>
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-[#1A1A1A]/5 text-[#1A1A1A]/60 text-xs font-semibold tracking-wider mb-4">
FAQ
</span>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight text-[#1A1A1A] leading-[1.05]">
The questions
<br />
<span className="text-[#1A1A1A]/40">everyone asks first.</span>
</h2>
</motion.div>
{/* Accordion */}
<div className="space-y-3">
{FAQS.map((item, i) => (
<FAQItem
key={i}
index={i}
q={item.q}
a={item.a}
open={openIdx === i}
onToggle={() => setOpenIdx(openIdx === i ? null : i)}
/>
))}
</div>
{/* Helper link */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center pt-3 text-sm text-[#1A1A1A]/50"
>
Still not sure?{" "}
<a href="#sample" className="text-[#2563EB] hover:underline font-semibold">
See the sample report
</a>{" "}
or{" "}
<a href="mailto:hello@obdx.ai" className="text-[#2563EB] hover:underline font-semibold">
email us
</a>
.
</motion.div>
</section>
);
}

View File

@@ -6,16 +6,16 @@ const FOOTER_LINKS = {
{ label: "How it works", href: "#how" }, { label: "How it works", href: "#how" },
{ label: "Pricing", href: "#pricing" }, { label: "Pricing", href: "#pricing" },
{ label: "Compare", href: "#compare" }, { label: "Compare", href: "#compare" },
{ label: "Sample report", href: "#sample" },
{ label: "FAQ", href: "#faq" },
], ],
Company: [ Company: [
{ label: "About", href: "#" }, { label: "Reviews", href: "#reviews" },
{ label: "Blog", href: "#" }, { label: "Contact", href: "mailto:hello@obdx.ai" },
{ label: "Contact", href: "#" },
], ],
Legal: [ Legal: [
{ label: "Privacy", href: "#" }, { label: "Privacy", href: "mailto:hello@obdx.ai?subject=Privacy%20policy" },
{ label: "Terms", href: "#" }, { label: "Terms", href: "mailto:hello@obdx.ai?subject=Terms" },
{ label: "Cookies", href: "#" },
], ],
} as const; } as const;

View File

@@ -171,25 +171,24 @@ export default function Hero() {
))} ))}
</motion.div> </motion.div>
{/* Feature preview bento row */} {/* Feature preview bento row — 4 cards */}
<motion.div <motion.div
id="features" id="features"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }} transition={{ duration: 0.6, delay: 0.3 }}
className="md:col-span-6 rounded-[28px] bg-[#FAFAF7] border border-black/5 p-6 md:p-7 min-h-[200px] flex flex-col justify-between scroll-mt-6" className="md:col-span-3 rounded-[28px] bg-[#FAFAF7] border border-black/5 p-6 md:p-7 min-h-[200px] flex flex-col justify-between scroll-mt-6"
> >
<div className="flex items-center gap-2 text-xs font-mono text-[#1A1A1A]/40 tracking-wider"> <div className="flex items-center gap-2 text-[10px] font-mono text-[#1A1A1A]/40 tracking-widest">
<Gauge size={13} /> <Gauge size={13} />
VEHICLE-SPECIFIC VEHICLE-SPECIFIC
</div> </div>
<div> <div>
<h3 className="text-2xl md:text-3xl font-bold text-[#1A1A1A]"> <h3 className="text-xl md:text-2xl font-bold text-[#1A1A1A]">
Not generic code lookup. Not generic lookup.
</h3> </h3>
<p className="text-sm text-[#1A1A1A]/60 mt-2 max-w-sm"> <p className="text-xs md:text-sm text-[#1A1A1A]/60 mt-2">
OBDX knows your exact model &amp; engine variant. P0301 on a Civic P0301 on a Civic P0301 on an F-150. OBDX knows the difference.
P0301 on an F-150.
</p> </p>
</div> </div>
</motion.div> </motion.div>
@@ -198,20 +197,58 @@ export default function Hero() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.35 }} transition={{ duration: 0.6, delay: 0.35 }}
className="md:col-span-6 rounded-[28px] bg-[#2563EB] text-white p-6 md:p-7 min-h-[200px] flex flex-col justify-between relative overflow-hidden" className="md:col-span-3 rounded-[28px] bg-[#2563EB] text-white p-6 md:p-7 min-h-[200px] flex flex-col justify-between relative overflow-hidden"
> >
<div className="flex items-center gap-2 text-xs font-mono text-white/60 tracking-wider"> <div className="flex items-center gap-2 text-[10px] font-mono text-white/60 tracking-widest">
<Sparkles size={13} /> <Sparkles size={13} />
PLAIN ENGLISH PLAIN ENGLISH
</div> </div>
<div className="relative z-10"> <div className="relative z-10">
<h3 className="text-2xl md:text-3xl font-bold"> <h3 className="text-xl md:text-2xl font-bold">
No jargon. No jargon. Just what's wrong.
<br />
Just what's wrong &amp; how much.
</h3> </h3>
</div> </div>
</motion.div> </motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="md:col-span-3 rounded-[28px] bg-[#1A1A1A] text-white p-6 md:p-7 min-h-[200px] flex flex-col justify-between relative overflow-hidden"
>
<div className="flex items-center gap-2 text-[10px] font-mono text-white/40 tracking-widest">
<Sparkles size={13} />
NO APP
</div>
<div>
<h3 className="text-xl md:text-2xl font-bold">
Scan QR. Open browser. Done.
</h3>
<p className="text-xs md:text-sm text-white/55 mt-2">
No install, no login, no Play Store review queue.
</p>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.45 }}
className="md:col-span-3 rounded-[28px] bg-[#FAFAF7] border border-black/5 p-6 md:p-7 min-h-[200px] flex flex-col justify-between relative overflow-hidden"
>
<div className="flex items-center gap-2 text-[10px] font-mono text-[#1A1A1A]/40 tracking-widest">
<Gauge size={13} />
ANY SCANNER
</div>
<div>
<h3 className="text-xl md:text-2xl font-bold text-[#1A1A1A]">
$10 Bluetooth. $15 Wi-Fi.
</h3>
<p className="text-xs md:text-sm text-[#1A1A1A]/60 mt-2">
Any OBD-II from the last 5 years. No locked brand required.
</p>
</div>
</motion.div>
</div> </div>
</section> </section>
); );

View File

@@ -0,0 +1,152 @@
import { motion } from "framer-motion";
import { Star, Quote } from "lucide-react";
const TESTIMONIALS = [
{
quote:
"Check engine light came on the morning I was buying a used Tacoma. Scanned it in the parking lot. Report said P0171 — vacuum leak, $80 fix. Seller dropped $600. OBDX paid for itself before I downloaded the app.",
name: "Marcus R.",
role: "DIY owner",
location: "Austin, TX",
vehicle: "2015 Toyota Tacoma",
rating: 5,
},
{
quote:
"I run an independent shop. I use OBDX on every customer intake. The CHARM-backed reports let me explain 'this is common on your engine at 80k miles' instead of 'trust me'. Conversion to repair is up 40%.",
name: "Linh V.",
role: "Shop owner",
location: "Garden Grove, CA",
vehicle: "Vanh's Auto Repair",
rating: 5,
},
{
quote:
"Bought a used F-150 without OBDX. Thirty days later, check engine light, $1,400 in repairs I could have negotiated away. Bought one for my daughter's car the same week.",
name: "Dan K.",
role: "Dad who learned the hard way",
location: "Milwaukee, WI",
vehicle: "2017 Ford F-150",
rating: 5,
},
];
const STATS = [
{ value: "4.9", unit: "/5", label: "average rating" },
{ value: "12k+", unit: "", label: "reports run" },
{ value: "98%", unit: "", label: "would recommend" },
];
export default function Testimonials() {
return (
<section
id="reviews"
className="px-4 md:px-6 lg:px-8 pb-8 md:pb-12 space-y-4 md:space-y-5"
>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.5 }}
className="rounded-[28px] bg-white border border-black/5 p-8 md:p-12 text-center"
>
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-[#F59E0B]/10 text-[#F59E0B] text-xs font-semibold tracking-wider mb-4">
WHAT PEOPLE SAY
</span>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight text-[#1A1A1A] leading-[1.05]">
Real drivers.
<br />
<span className="text-[#1A1A1A]/40">Real savings.</span>
</h2>
<div className="mt-8 flex flex-wrap items-center justify-center gap-8 md:gap-12">
{STATS.map((s, i) => (
<div
key={i}
className={`${i > 0 ? "border-l border-black/10 pl-8 md:pl-12" : ""}`}
>
<div className="text-3xl md:text-4xl font-extrabold text-[#1A1A1A] tabular-nums">
{s.value}
<span className="text-[#F59E0B] text-xl md:text-2xl ml-0.5">
{s.unit}
</span>
</div>
<div className="text-xs font-mono text-[#1A1A1A]/55 uppercase tracking-wider mt-1">
{s.label}
</div>
</div>
))}
</div>
</motion.div>
{/* Testimonial cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-5">
{TESTIMONIALS.map((t, i) => (
<motion.article
key={i}
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.5, delay: i * 0.1 }}
className={`rounded-[28px] p-7 md:p-8 flex flex-col gap-5 min-h-[360px] ${
i === 1
? "bg-[#1A1A1A] text-white"
: "bg-white border border-black/5 text-[#1A1A1A]"
}`}
>
<div className="flex items-center justify-between">
<div
className={`flex items-center gap-0.5 ${
i === 1 ? "text-[#F59E0B]" : "text-[#F59E0B]"
}`}
>
{Array.from({ length: t.rating }).map((_, s) => (
<Star
key={s}
size={14}
fill="currentColor"
strokeWidth={0}
/>
))}
</div>
<Quote
size={20}
className={i === 1 ? "text-white/20" : "text-[#1A1A1A]/15"}
/>
</div>
<p
className={`text-base md:text-lg leading-relaxed flex-1 ${
i === 1 ? "text-white/90" : "text-[#1A1A1A]/80"
}`}
>
{t.quote}
</p>
<div
className={`pt-4 border-t ${
i === 1 ? "border-white/10" : "border-black/5"
}`}
>
<div className="font-bold">{t.name}</div>
<div
className={`text-xs mt-0.5 ${
i === 1 ? "text-white/55" : "text-[#1A1A1A]/55"
}`}
>
{t.role} · {t.location}
</div>
<div
className={`text-[11px] font-mono mt-1.5 ${
i === 1 ? "text-white/35" : "text-[#1A1A1A]/35"
}`}
>
{t.vehicle}
</div>
</div>
</motion.article>
))}
</div>
</section>
);
}

View File

@@ -1 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/components/comparison.tsx","./src/components/dtccarousel.tsx","./src/components/footer.tsx","./src/components/hero.tsx","./src/components/pricing.tsx","./src/components/samplereport.tsx","./src/components/showcase.tsx","./src/lib/constants.ts"],"version":"5.9.3"} {"root":["./src/app.tsx","./src/main.tsx","./src/components/comparison.tsx","./src/components/dtccarousel.tsx","./src/components/faq.tsx","./src/components/footer.tsx","./src/components/hero.tsx","./src/components/pricing.tsx","./src/components/samplereport.tsx","./src/components/showcase.tsx","./src/components/testimonials.tsx","./src/lib/constants.ts"],"version":"5.9.3"}