Back to 0fee
0fee

A Stripe-Inspired Marketing Website in SolidJS

How we built 0fee.dev's Stripe-inspired marketing website with interactive code examples, pricing calculator, and country map.

Thales & Claude | March 25, 2026 9 min 0fee
marketingsolidjsdesignglassmorphismintersection-observer

Stripe's website is the gold standard for developer-focused marketing. Clean typography, interactive code examples, smooth animations, and information density that respects the reader's intelligence. When we built 0fee.dev's marketing website, we studied Stripe's approach and adapted it for a payment orchestrator targeting Africa.

This article covers the marketing website's major sections -- the hero with live stats, interactive code examples in five languages, the features grid, the interactive country map, the pricing calculator, and the supporting pages. Everything is built in SolidJS with TailwindCSS, using Intersection Observer for scroll-triggered animations and glassmorphism for depth.

The Home Page Structure

The marketing home page (Home.tsx) assembles eight sections in order:

tsxexport default function Home() {
  return (
    <div>
      <Hero />
      <Features />
      <Providers />
      <CodeExamples />
      <Countries />
      <Pricing />
      <Testimonials />
      <CTA />
    </div>
  );
}

Each section is a self-contained component with its own data, animations, and responsive layout.

Hero Section with Stats

The hero combines a headline, subtitle, and real-time statistics:

tsxfunction Hero() {
  const stats = [
    { value: "53+", label: "Payment Providers" },
    { value: "200+", label: "Countries Covered" },
    { value: "40+", label: "Currencies" },
    { value: "99.9%", label: "Uptime" },
  ];

  return (
    <section class="hero-gradient min-h-screen flex items-center">
      <div class="container mx-auto px-6 text-center">
        <div class="inline-block px-4 py-1 bg-emerald-500/10 rounded-full
                    text-emerald-400 text-sm mb-6">
          Payment Orchestration for Developers
        </div>

        <h1 class="text-5xl md:text-7xl font-bold text-white mb-6">
          One API.
          <span class="gradient-text"> Every Payment.</span>
          <br />Everywhere.
        </h1>

        <p class="text-xl text-gray-400 max-w-2xl mx-auto mb-10">
          Accept cards, mobile money, and wallets across 200+ countries
          with a single integration. Africa-first, globally ready.
        </p>

        <div class="flex gap-4 justify-center mb-16">
          <a href="/register" class="btn-primary">Start Building</a>
          <a href="/docs" class="btn-secondary">Read the Docs</a>
        </div>

        <div class="grid grid-cols-2 md:grid-cols-4 gap-8">
          <For each={stats}>
            {(stat) => (
              <div class="text-center">
                <div class="text-3xl font-bold text-white">{stat.value}</div>
                <div class="text-gray-400 text-sm">{stat.label}</div>
              </div>
            )}
          </For>
        </div>
      </div>
    </section>
  );
}

The hero uses a dark gradient background (hero-gradient) with decorative gradient orbs that float with a CSS animation. The stats row provides social proof immediately.

Interactive Code Examples

The code examples section is the most Stripe-inspired component. It shows integration code in five programming languages with syntax highlighting, line numbers, and a copy button:

tsxfunction CodeExamples() {
  const [activeTab, setActiveTab] = createSignal("typescript");

  const examples = {
    typescript: {
      label: "TypeScript",
      install: "npm install zerofee",
      code: `import { ZeroFee } from 'zerofee';

const zf = new ZeroFee({ apiKey: 'zf_live_...' });

const payment = await zf.payments.create({
  amount: 5000,
  currency: 'XOF',
  country: 'CI',
  method: 'PAYIN_ORANGE_CI',
  customer: { phone: '+2250700000000' },
  returnUrl: 'https://yourapp.com/thanks'
});

// Redirect customer to checkout
window.location.href = payment.checkoutUrl;`,
    },
    python: {
      label: "Python",
      install: "pip install zerofee",
      code: `from zerofee import ZeroFee

zf = ZeroFee(api_key="zf_live_...")

payment = zf.payments.create(
    amount=5000,
    currency="XOF",
    country="CI",
    method="PAYIN_ORANGE_CI",
    customer={"phone": "+2250700000000"},
    return_url="https://yourapp.com/thanks"
)

# Redirect customer to checkout
print(payment.checkout_url)`,
    },
    curl: {
      label: "cURL",
      install: "",
      code: `curl -X POST https://api.0fee.dev/v1/payments \\
  -H "Authorization: Bearer zf_live_..." \\
  -H "Content-Type: application/json" \\
  -d '{
    "amount": 5000,
    "currency": "XOF",
    "country": "CI",
    "payment_method": "PAYIN_ORANGE_CI",
    "customer": {"phone": "+2250700000000"},
    "return_url": "https://yourapp.com/thanks"
  }'`,
    },
    go: {
      label: "Go",
      install: "go get github.com/zerosuite/zerofee-go",
      code: `package main

import "github.com/zerosuite/zerofee-go"

func main() {
    zf := zerofee.New("zf_live_...")

    payment, _ := zf.Payments.Create(&zerofee.PaymentParams{
        Amount:   5000,
        Currency: "XOF",
        Country:  "CI",
        Method:   "PAYIN_ORANGE_CI",
        Customer: &zerofee.Customer{
            Phone: "+2250700000000",
        },
        ReturnURL: "https://yourapp.com/thanks",
    })

    fmt.Println(payment.CheckoutURL)
}`,
    },
    php: {
      label: "PHP",
      install: "composer require zerosuite/zerofee-php",
      code: `<?php
use ZeroFee\\ZeroFee;

$zf = new ZeroFee('zf_live_...');

$payment = $zf->payments->create([
    'amount' => 5000,
    'currency' => 'XOF',
    'country' => 'CI',
    'method' => 'PAYIN_ORANGE_CI',
    'customer' => ['phone' => '+2250700000000'],
    'return_url' => 'https://yourapp.com/thanks',
]);

header('Location: ' . $payment->checkout_url);`,
    },
  };

  return (
    <section class="py-24 bg-gray-950">
      <div class="container mx-auto px-6">
        <h2 class="text-3xl font-bold text-white text-center mb-4">
          Five Lines of Code
        </h2>
        <p class="text-gray-400 text-center mb-12 max-w-xl mx-auto">
          Accept payments worldwide with a single API call.
          Choose your language.
        </p>

        {/* Language tabs */}
        <div class="flex gap-2 justify-center mb-8">
          <For each={Object.entries(examples)}>
            {([key, lang]) => (
              <button
                class={`px-4 py-2 rounded-lg text-sm ${
                  activeTab() === key
                    ? "bg-emerald-500 text-white"
                    : "text-gray-400 hover:text-white"
                }`}
                onClick={() => setActiveTab(key)}
              >
                {lang.label}
              </button>
            )}
          </For>
        </div>

        {/* Code block with syntax highlighting */}
        <div class="max-w-3xl mx-auto">
          <SyntaxHighlighter
            code={examples[activeTab()].code}
            language={activeTab()}
          />
        </div>
      </div>
    </section>
  );
}

The tab switching is instant -- all five code blocks are pre-rendered and toggled with CSS. No loading, no API calls.

Features Grid

The features section presents eight capabilities in a responsive grid:

tsxconst features = [
  {
    title: "Smart Routing",
    description: "Automatically route payments to the best provider based on country, method, and availability.",
    icon: RouteIcon,
  },
  {
    title: "Mobile Money First",
    description: "Orange Money, MTN, Wave, M-Pesa, and 40+ operators across Africa.",
    icon: PhoneIcon,
  },
  {
    title: "One Integration",
    description: "Single API, single SDK, single webhook format. Cover 200+ countries.",
    icon: CodeIcon,
  },
  {
    title: "Real-Time Dashboard",
    description: "Monitor transactions, manage providers, and track revenue in one place.",
    icon: ChartIcon,
  },
  // ... 4 more features
];

Each feature card uses Intersection Observer for scroll-triggered fade-in animation:

tsxfunction FeatureCard(props) {
  let ref;

  onMount(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          ref.classList.add("animate-fade-in");
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );
    observer.observe(ref);
  });

  return (
    <div ref={ref} class="opacity-0 p-6 rounded-xl bg-gray-900 border border-gray-800">
      <props.icon class="w-8 h-8 text-emerald-400 mb-4" />
      <h3 class="text-lg font-semibold text-white mb-2">{props.title}</h3>
      <p class="text-gray-400 text-sm">{props.description}</p>
    </div>
  );
}

Interactive Country Map

The Countries component displays 30+ countries with region filtering:

tsxfunction Countries() {
  const [activeRegion, setActiveRegion] = createSignal("all");

  const regions = [
    { id: "all", label: "All" },
    { id: "west", label: "West Africa" },
    { id: "east", label: "East Africa" },
    { id: "central", label: "Central" },
    { id: "south", label: "Southern" },
    { id: "global", label: "Global" },
  ];

  const countries = [
    { code: "CI", name: "Ivory Coast", region: "west",
      methods: ["Orange", "MTN", "Wave", "Moov"] },
    { code: "KE", name: "Kenya", region: "east",
      methods: ["M-Pesa", "Airtel"] },
    { code: "NG", name: "Nigeria", region: "west",
      methods: ["MTN", "Airtel", "Cards"] },
    // ... 27+ more countries
  ];

  const filtered = () =>
    activeRegion() === "all"
      ? countries
      : countries.filter(c => c.region === activeRegion());

  return (
    <section class="py-24">
      <div class="container mx-auto px-6">
        <h2 class="text-3xl font-bold text-center mb-12">
          Coverage Across 200+ Countries
        </h2>

        {/* Region tabs */}
        <div class="flex gap-2 justify-center mb-8">
          <For each={regions}>
            {(region) => (
              <button
                class={`px-4 py-2 rounded-full text-sm ${
                  activeRegion() === region.id
                    ? "bg-emerald-500 text-white"
                    : "bg-gray-800 text-gray-400"
                }`}
                onClick={() => setActiveRegion(region.id)}
              >
                {region.label}
              </button>
            )}
          </For>
        </div>

        {/* Country grid */}
        <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
          <For each={filtered()}>
            {(country) => (
              <div class="p-4 rounded-xl bg-gray-900 border border-gray-800
                          hover:border-emerald-500 transition-colors group">
                <div class="text-2xl mb-2">{country.flag}</div>
                <div class="text-white font-medium">{country.name}</div>
                <div class="text-xs text-gray-500 mt-1">
                  {country.methods.join(", ")}
                </div>
              </div>
            )}
          </For>
        </div>
      </div>
    </section>
  );
}

Hovering over a country card reveals the available payment methods -- Orange Money, MTN, Wave, etc.

Pricing with Calculator

The pricing section features an interactive fee calculator:

tsxfunction PricingCalculator() {
  const [volume, setVolume] = createSignal(1000);
  const [avgAmount, setAvgAmount] = createSignal(50);

  const monthlyRevenue = () => volume() * avgAmount();
  const zerofeeTotal = () => monthlyRevenue() * 0.0099; // 0.99%

  return (
    <div class="bg-gray-900 rounded-xl p-8">
      <h3 class="text-xl font-bold text-white mb-6">Fee Calculator</h3>

      <div class="space-y-6">
        <div>
          <label class="text-gray-400 text-sm">
            Monthly Transactions: {volume().toLocaleString()}
          </label>
          <input
            type="range"
            min="100" max="100000" step="100"
            value={volume()}
            onInput={(e) => setVolume(parseInt(e.target.value))}
            class="w-full"
          />
        </div>

        <div>
          <label class="text-gray-400 text-sm">
            Average Amount: ${avgAmount()}
          </label>
          <input
            type="range"
            min="1" max="500" step="1"
            value={avgAmount()}
            onInput={(e) => setAvgAmount(parseInt(e.target.value))}
            class="w-full"
          />
        </div>

        <div class="border-t border-gray-800 pt-4">
          <div class="flex justify-between text-gray-400">
            <span>Monthly Volume</span>
            <span>${monthlyRevenue().toLocaleString()}</span>
          </div>
          <div class="flex justify-between text-white text-lg font-bold mt-2">
            <span>0fee Cost (0.99%)</span>
            <span>${zerofeeTotal().toLocaleString()}</span>
          </div>
        </div>
      </div>
    </div>
  );
}

The calculator uses SolidJS signals for instant reactivity -- moving the slider updates the fee estimate without any re-rendering of the surrounding DOM.

Glassmorphism Effects

Throughout the marketing site, we use glassmorphism for visual depth:

css.glass {
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.glass-hover:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.15);
}

These effects are used on cards, modals, and decorative elements. They create a sense of layering without solid backgrounds.

Additional Pages

Beyond the home page, the marketing site includes:

PageRouteContent
Products/productsProduct overview (Payments, Checkout, Routing)
Pricing/pricingDetailed pricing with FAQ accordion
About/aboutTeam, mission, ZeroSuite ecosystem
Contact/contactContact form with office info
Docs/docsGetting started guide with SDK examples
Status/statusProvider health status indicators
How It Works/how-it-worksStep-by-step payment flow explanation
Coverage/coverageDetailed country and method coverage
Providers/providersAll 53+ payment providers

Intersection Observer Animations

Every section uses Intersection Observer for scroll-triggered entrance animations:

typescriptfunction useScrollAnimation() {
  return (el: HTMLElement) => {
    el.classList.add("opacity-0", "translate-y-4");

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          el.classList.remove("opacity-0", "translate-y-4");
          el.classList.add("opacity-100", "translate-y-0", "transition-all", "duration-700");
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

    observer.observe(el);
  };
}

Elements start invisible and translated 16px down. When they scroll into view, they fade in and slide up. The observer disconnects after triggering to avoid unnecessary processing.

What We Learned

Building the marketing website taught us three things:

  1. Interactive elements convert better than static text. The code examples with tab switching, the pricing calculator with sliders, and the country map with region filters all invite interaction. Each interaction reinforces the product's capabilities.
  1. Dark themes work for developer-focused products. The dark gradient backgrounds with emerald accents create a premium feel while reducing eye strain for developers who spend hours reading documentation.
  1. Performance matters for first impressions. The marketing site loads in under 2 seconds. Every section is visible without waiting for data fetches (all content is static). Animations are CSS-based, not JavaScript-driven, so they run at 60fps even on mobile devices.

The marketing website was built in Session 003 with a complete redesign in Session 014. It draws direct inspiration from Stripe's approach -- information-dense, developer-focused, and visually polished -- while adapting the content for a pan-African payment orchestrator.


This article is part of the "How We Built 0fee.dev" series. 0fee.dev is a payment orchestrator covering 53+ providers across 200+ countries, built by Juste A. GNIMAVO and Claude from Abidjan with zero human engineers. Follow the series for the complete build story.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles