Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Caramel - Trusted Coupon Finder

**Caramel is the open‑source, privacy‑first alternative to Honey.**
It automatically tests codes at checkout, never sells your data, and never overwrites creators’ affiliate links. Today Caramel already supports more than **5 000 stores**. You can find even more information at https://grabcaramel.com.
It automatically tests codes at checkout, never sells your data, and never overwrites creators’ affiliate links. Today Caramel already supports more than **4,000 stores**. You can find even more information at https://grabcaramel.com.

<a href="https://grabcaramel.com">
<img width="300" height="180" alt="caramel-banner" src="https://grabcaramel.com/caramel_banner.png" />
Expand All @@ -14,8 +14,8 @@ It automatically tests codes at checkout, never sells your data, and never overw
## Why choose Caramel?

- **100% open source** – every release is on GitHub for public audit
- **Privacy first** – the extension only asks for tab access while you shop, nothing more
- **Real‑time savings meter** – see exactly how much a coupon saves you before you commit
- **Privacy first** – the extension asks only for the permissions it needs to find the store you're on, read the checkout page and apply codes (tabs, storage, sign-in) – no ads, no third‑party trackers
- **Real‑time savings meter** – see exactly how much each code saved as it is tested
- **Creator‑friendly** – Caramel keeps existing affiliate links intact so influencers get the credit they deserve
- **Community‑powered** – join our Discord, file issues, or open PRs to make Caramel even sweeter

Expand Down
2 changes: 1 addition & 1 deletion apps/caramel-app/e2e/home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test.describe('Home Page - Critical Sections', () => {
page.getByRole('heading', { name: 'Creator-Friendly' }),
).toBeVisible()
await expect(
page.getByRole('heading', { name: 'Lightning Fast' }),
page.getByRole('heading', { name: 'Fast & Lightweight' }),
).toBeVisible()
await expect(
page.getByRole('heading', { name: 'Cross-Browser Support' }),
Expand Down
2 changes: 1 addition & 1 deletion apps/caramel-app/e2e/pages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test.describe('Coupons Page', () => {
await expect(
page.getByRole('heading', {
level: 1,
name: /verified coupon codes/i,
name: /today's coupon codes/i,
}),
).toBeVisible()
await expect(page.getByText(/browse.*coupon/i).first()).toBeVisible()
Expand Down
27 changes: 27 additions & 0 deletions apps/caramel-app/e2e/seo-regression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,33 @@ test.describe('SEO regression gate (raw server HTML)', () => {
expect(types).toContain('"@type":"SoftwareApplication"')
})

// Claim-integrity pin (2026-09-11): the two catalog figures the site is
// allowed to quote are "4,000+ stores" and "100,000+ coupon codes",
// rounded DOWN from prod on that date (4,314 store pages in sitemap.xml,
// 107,827 from GET /api/coupons?limit=1 → total). Both are static copy
// (heroStats.ts, FaqSection.tsx, PricingSection.tsx, SupportedSection.tsx)
// so this holds in both e2e contexts. The negative pin is the retired
// "139,000" FAQ number that outlived the stats-census change for six
// weeks — a figure must never drift back silently. Re-verify the live
// counts before changing either string; never round up.
test('home raw HTML quotes only the reconciled catalog figures', async ({
page,
}) => {
const html = await (await page.request.get('/')).text()
expect(html, 'store count must read 4,000+').toContain('4,000+')
expect(html, 'coupon count must read 100,000').toContain(
'over 100,000 coupon codes across more than 4,000 online stores',
)
expect(
html,
'retired FAQ figure 139,000 must not reappear',
).not.toContain('139,000')
expect(
html,
'superseded store count 3,000+ must not reappear',
).not.toContain('3,000+')
})

test('robots.txt honours the env-aware indexing contract', async ({
baseURL,
page,
Expand Down
8 changes: 4 additions & 4 deletions apps/caramel-app/src/app/(marketing)/coupons/[store]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export async function generateMetadata({
// here or these pages share links with no preview image at all.
const banner = `${baseUrl}/caramel_banner.png`
const title = `${base} Coupons & Promo Codes | Caramel`
const description = `Find verified ${base} coupon codes, promo codes, and discounts. Updated daily.`
const description = `Find ${base} coupon codes, promo codes, and discounts — refreshed as new codes are found.`
// Canonical always points at the NORMALIZED base-domain URL: this route
// serves the same content for /coupons/www.nike.com, /coupons/shop.nike.com
// and /coupons/nike.com, so every variant must canonicalize to ONE URL or
Expand Down Expand Up @@ -238,9 +238,9 @@ export default async function StoreCouponsPage({
codes for that store from its own catalog, tries them in the
promo-code field, and keeps the one with the biggest
discount. It never replaces affiliate links, and it reports
back only whether a code worked — with no account
information attached — so code rankings stay accurate for
every shopper.
back whether a code worked (linked to your account only when
you're signed in) so code rankings stay accurate for every
shopper.
</p>
</section>
<PopularStores currentSite={base} />
Expand Down
8 changes: 4 additions & 4 deletions apps/caramel-app/src/app/(marketing)/coupons/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PAGE_SIZE = 5

const title = 'Coupon Codes & Promo Codes for Top Stores | Caramel'
const description =
'Browse verified coupon codes and promo codes for your favorite stores. Caramel finds and applies the best deals automatically at checkout.'
'Browse coupon codes and promo codes for your favorite stores. Caramel finds and applies the best deals automatically at checkout.'
const canonicalUrl = 'https://grabcaramel.com/coupons'
const banner = `${BASE_URL}/caramel_banner.png`

Expand Down Expand Up @@ -64,7 +64,7 @@ export default async function CouponsPage() {
const structuredData = {
'@context': 'https://schema.org',
'@type': 'ItemList',
name: 'Verified coupon codes and promo codes',
name: 'Coupon codes and promo codes',
url: canonicalUrl,
numberOfItems: total,
itemListElement: couponsWithSignals.map((coupon, idx) => ({
Expand All @@ -82,8 +82,8 @@ export default async function CouponsPage() {
initialCoupons={couponsWithSignals}
initialTotal={total}
disableInitialFetch
heroTitle="Today's Verified Coupon Codes"
heroSubtitle="Browse verified coupon codes, promo codes, and offers for your favorite stores."
heroTitle="Today's Coupon Codes"
heroSubtitle="Browse coupon codes, promo codes, and offers for your favorite stores."
/>
<PopularStores />
<script
Expand Down
2 changes: 1 addition & 1 deletion apps/caramel-app/src/app/(marketing)/sources/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const dynamic = 'force-dynamic'

const title = 'Where Caramel Coupon Codes Come From | Sources'
const description =
'See every source Caramel pulls coupon codes from, how many codes each one contributes, and its success rate — or request a new source.'
"Where Caramel's coupon codes come from: the sources feeding the catalog, with the code count and success rate of each one as it is published — or request a new source."
const base = BASE_URL
const canonicalUrl = `${base}/sources`
const banner = `${base}/caramel_banner.png`
Expand Down
8 changes: 4 additions & 4 deletions apps/caramel-app/src/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const LLMS_TXT = `# Caramel
- [Coupons](${origin}/coupons): browse the full coupon catalog.
- [Store coupon pages](${origin}/coupons/amazon.com): per-store codes, one page
per store domain, e.g. /coupons/amazon.com or /coupons/nike.com.
- [Supported stores](${origin}/supported-stores): which stores Caramel can
auto-apply codes on.
- [Sources](${origin}/sources): the transparency page listing where Caramel's
coupon codes come from.
- [Supported stores](${origin}/supported-stores): which stores Caramel holds
coupon codes for.
- [Sources](${origin}/sources): where Caramel's coupon codes come from, and
where to request a new source.
- [Privacy policy](${origin}/privacy): what data Caramel does and does not
collect.

Expand Down
10 changes: 6 additions & 4 deletions apps/caramel-app/src/components/FaqSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { FaChevronDown } from 'react-icons/fa'
// + cart-signals.js payload (title/meta/up to 6 item names, no payment data);
// sign-in token lives in browser extension storage (popup.js/coupon-runner.js).
// - browsers: the four live store listings in src/lib/brandLinks.ts.
// - numbers: 139,340 active codes / 3,402 distinct stores from the PROD
// /api/coupons/stats + catalog on 2026-07-28, rounded DOWN. Never round up.
// - numbers: 107,827 visible codes (GET /api/coupons?limit=1 → total) /
// 4,314 stores with live codes (sitemap /coupons/<store> URLs) from PROD on
// 2026-09-11, rounded DOWN. Never round up, and never derive the catalog
// size from /api/coupons/stats (a valid-only trust census that undercounts).
const faqItems = [
{
question: 'Does Caramel replace or hijack creator affiliate links?',
Expand All @@ -28,7 +30,7 @@ const faqItems = [
},
{
question: 'What data does the Caramel extension collect?',
answer: "The extension never sells or shares your personal information, and it contains no ads and no third-party trackers. To do its job it talks to Caramel's own servers: when you reach checkout on a supported store it fetches coupon codes for that store's domain, sends the page and cart context (page title and item names — never payment details) so the right category of codes is chosen, and reports whether a code worked so rankings stay accurate for everyone. Your settings and optional sign-in are kept in your browser's extension storage.",
answer: "The extension never sells or shares your personal information, and it contains no ads and no third-party trackers. To do its job it talks to Caramel's own servers: as you browse, it asks whether Caramel has codes for the current site's domain so the toolbar badge can show a count; when you reach checkout on a supported store it fetches those codes, sends the page and cart context (page title and item names — never payment details) so the right category of codes is chosen, and reports whether a code worked (linked to your account only if you're signed in) so rankings stay accurate for everyone. Your settings and optional sign-in are kept in your browser's extension storage.",
},
{
question: 'How is Caramel different from Honey?',
Expand All @@ -40,7 +42,7 @@ const faqItems = [
},
{
question: 'How many coupon codes does Caramel have?',
answer: "Caramel's catalog holds over 139,000 active coupon codes across more than 3,000 online stores, and it is refreshed continuously as new codes are found and dead ones are retired.",
answer: "Caramel's catalog holds over 100,000 coupon codes across more than 4,000 online stores, and it is refreshed continuously as new codes are found and dead ones are retired.",
},
{
question: 'Do I need an account to use Caramel?',
Expand Down
22 changes: 11 additions & 11 deletions apps/caramel-app/src/components/FeaturesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const features = [
{
title: 'Automated Coupon Application',
desc: 'Saves you money by finding and applying the best coupon codes.',
detail: 'We monitor thousands of coupon codes across 3,000+ stores, ensuring you always get the best deal without lifting a finger.',
detail: 'We monitor thousands of coupon codes across 4,000+ stores, ensuring you always get the best deal without lifting a finger.',
icon: <FaDollarSign />,
},
{
Expand All @@ -42,44 +42,44 @@ const features = [
icon: <FaHeart />,
},
{
title: 'Lightning Fast',
desc: 'Instant coupon discovery and application at checkout.',
detail: 'Our optimized algorithms search and apply coupons in seconds, never slowing down your shopping experience.',
title: 'Fast & Lightweight',
desc: 'Automatic coupon discovery and application at checkout.',
detail: 'Our optimized algorithms search and apply coupons at checkout, never slowing down your shopping experience.',
icon: <FaBolt />,
},
{
title: 'Cross-Browser Support',
desc: 'Works seamlessly across Chrome, Firefox, and more.',
detail: 'One extension, all browsers. Consistent experience regardless of your browser preference with automatic updates.',
detail: 'One extension for Chrome, Firefox, Edge, and Safari. Consistent experience regardless of your browser preference with automatic updates.',
icon: <FaGlobe />,
},
]

const comparisonItems = [
{
title: 'Privacy Protection',
other: 'Opaque data practices',
other: 'Often opaque data practices',
caramel: '100% privacy-first approach',
},
{
title: 'Data Collection',
other: 'Tracks your browsing habits',
other: 'May track your browsing habits',
caramel: 'No ad tracking or data selling',
},
{
title: 'Creator Support',
other: 'Hijacks affiliate commissions',
other: 'Hijacked affiliate commissions',
caramel: 'Protects creator earnings',
},
{
title: 'Code Transparency',
other: 'Closed source & hidden',
caramel: 'Fully open source & peer-reviewed',
caramel: 'Fully open source & publicly reviewable',
},
{
title: 'Performance',
other: 'Slow and resource-heavy',
caramel: 'Lightning fast & lightweight',
other: 'Often resource-heavy',
caramel: 'Fast & lightweight',
},
]

Expand Down
4 changes: 2 additions & 2 deletions apps/caramel-app/src/components/HeroTicketScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const STAT_FRONT = STAT.depth / 2 + STAT.bevel
// the third (no straight line through the scatter). Varied x/y/z, individual
// base rotations and per-coupon float parameters keep it reading as coupons
// drifting in space:
// [0] "3,000+ Supported Stores" — bottom-left, nearest the camera and the
// [0] "4,000+ Supported Stores" — bottom-left, nearest the camera and the
// largest (the headline stat). Its top-right corner deliberately fans
// OVER the deeper [2] ticket's blank bottom-left corner — a coupon-
// stack moment, kept clear of [2]'s type (see the occlusion contract
Expand Down Expand Up @@ -423,7 +423,7 @@ interface MeasuredText extends THREE.Mesh {

// Digits + smaller suffix, centered AS A PAIR from LIVE troika measurements.
// The value counts up from 0, so the digit run's width changes every frame
// ("0" → "3,000") — hard-coded offsets would only fit the final number.
// ("0" → "4,000") — hard-coded offsets would only fit the final number.
// onSync fires after every glyph-layout pass; re-centering there keeps the
// pair balanced mid-count-up AND at rest.
function StatValueText({
Expand Down
11 changes: 6 additions & 5 deletions apps/caramel-app/src/components/OpenSourceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const contributions = [
const securityFeatures = [
{
title: 'Open Security Reviews',
desc: 'Every change is peer-reviewed in public',
desc: 'Every change is publicly reviewable',
icon: <FaLock />,
},
{
Expand All @@ -85,7 +85,7 @@ const securityFeatures = [
},
{
title: 'No Ad Tracking',
desc: 'We never sell or share your personal information',
desc: 'We never sell your personal information or share it with advertisers',
icon: <FaBan />,
},
{
Expand Down Expand Up @@ -119,9 +119,10 @@ export default function OpenSourceSection() {
Open Source & Community Driven
</h2>
<p className="mx-auto max-w-3xl text-xl leading-relaxed text-gray-600 dark:text-gray-300 lg:text-lg">
Transparency is our core. Every line of code is open,
auditable, and community-driven. Join the developers
building a better alternative to proprietary extensions.
Transparency is our core. The extension and web app are
open, auditable, and community-driven. Join the
developers building a better alternative to proprietary
extensions.
</p>
</motion.div>

Expand Down
10 changes: 5 additions & 5 deletions apps/caramel-app/src/components/PricingSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { HiCheckCircle } from 'react-icons/hi'

const features = [
'Automated coupon application at checkout',
'Works on 3,000+ supported stores',
'Works on 4,000+ supported stores',
'Zero ads or data selling',
'Open source & community-driven',
'Never hijacks affiliate commissions',
'Lightning fast performance',
'Fast, lightweight performance',
'Cross-browser support (Chrome, Firefox, Edge, iOS Safari)',
'Regular updates & new features',
'No credit card required',
Expand All @@ -29,7 +29,7 @@ const stats = [
icon: <FaGithub />,
},
{
title: '3,000+',
title: '4,000+',
desc: 'Stores Supported',
icon: <FaHeart />,
},
Expand Down Expand Up @@ -299,8 +299,8 @@ export default function PricingSection() {
We believe in transparency.
</strong>{' '}
Unlike other extensions that monetize your data or
hijack affiliate commissions, Caramel is built by
the community, for the community.
hijack affiliate commissions, Caramel is built in
the open, for the community.
</motion.p>
<motion.p
initial={{ opacity: 0 }}
Expand Down
4 changes: 2 additions & 2 deletions apps/caramel-app/src/components/SupportedSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ export default function SupportedSection() {
className="mb-20 text-center"
>
<h2 className="mb-8 bg-gradient-to-r from-caramel to-orange-600 bg-clip-text pb-1 text-5xl font-extrabold leading-tight tracking-tight text-transparent lg:text-4xl">
3,000+ Supported Stores
4,000+ Supported Stores
</h2>
<p className="max-w mx-auto text-xl leading-relaxed text-gray-600 dark:text-gray-300 lg:text-lg">
From major retailers to niche marketplaces, Caramel
works everywhere you shop online.
works at thousands of stores.
</p>
<motion.div className="mt-8">
<motion.a
Expand Down
Loading
Loading