Skip to content

1440 × 900

Tessera

A light, detailed SaaS landing page for a made-up finance workspace — gold light pooling on cream, the product standing in it as a stack of real surfaces, and a four-part tour of it.

A whole landing page — nav, hero, a product tour, six capabilities, a customer quote, pricing, FAQ, close and footer — in a light register: Iris's Amber Glow pools warm gold behind the hero with its reading guard on the copy, and the product stands in it as a stack of real surfaces — a balance with its ninety days, a card, a paid invoice. The same gold rhymes through the page: the card's tile, the forecast line, the list marks, the close. Depth is layering — white surfaces on cream, a lit top edge, two shadows, one near and one far — and hierarchy is three strengths of one ink; money is set in tabular figures.

The detail is in the product, not the ornament: the tour switches between four working surfaces — accounts, cards, invoices, the month-end close — as accessible tabs (the page's one client component); each capability is drawn as its own small piece of UI; pricing switches monthly and annual with no script. Self-contained: its colours are a few variables scoped to .tessera, and it takes your site's typeface. The customer quote is placeholder copy — replace it with a real one or remove it — and a finance product should say plainly who holds the money; add that before it ships.

Details

Kind
Landing
Stack
React · TypeScript
Depends on
Nothing
Licence
Free
Added
28 Sep 2026
Tags
saas, landing-page, light, fintech, product-tour, pricing, faq, webgl-hero

Its light

  • 6%
  • 5%
  • 12%
  • 53%
  • 24%

Code

Install

No installation needed — self-contained, paste-in code.

Usage

example.tsx
import { TesseraLanding } from "./tessera/TesseraLanding";

// The whole page — drop it into a route and replace the copy.
export default function Page() {
  return <TesseraLanding />;
}

Component

The real source, exactly as it ships — multiple files, kept together.

import type { ReactNode } from "react";
import { AmberGlowField } from "@/components/iris/visuals/backgrounds/AmberGlowField";
import { TesseraTour } from "./TesseraTour";
import { Avatar, Chip, Forecast, Mark, Money, Spark } from "./parts";
import "./tessera.css";

/**
 * Tessera — a light SaaS landing page, from Iris's Templates.
 *
 * A whole page for a (made-up) finance workspace. Its one light is gold:
 * Iris's `AmberGlowField` pools warm light on a cream ground behind the
 * hero, the product stands in it as a stack of real surfaces — a balance,
 * a card, a paid invoice — and the same gold rhymes through the page: the
 * card's tile, the forecast line, the close. Depth is layering, not
 * decoration; hierarchy is three strengths of one ink.
 *
 * Detail lives in the product, not in ornament: a tour of four real
 * surfaces (`TesseraTour`, the page's one client component), six
 * capabilities each drawn as its own piece of UI, pricing that switches
 * monthly/annual with no script, a quote, the FAQ. The quote is
 * placeholder copy — replace it with a real customer's, or remove it.
 */

const NAV = [
  { label: "Product", href: "#tour" },
  { label: "Features", href: "#features" },
  { label: "Pricing", href: "#pricing" },
  { label: "FAQ", href: "#faq" },
];

function Label({ children }: { children: ReactNode }) {
  return <p className="text-[0.8125rem] font-medium text-[color:var(--ts-gold)]">{children}</p>;
}

function Title({ id, children, className = "" }: { id?: string; children: ReactNode; className?: string }) {
  return (
    <h2 id={id} className={`mt-3 text-[clamp(2rem,1.3rem+2.4vw,3.25rem)] leading-[1.04] font-semibold tracking-[-0.035em] text-balance ${className}`}>
      {children}
    </h2>
  );
}

/* ---- nav ------------------------------------------------------------ */

function Nav() {
  return (
    <header className="ts-nav sticky top-0 z-30 border-b border-[color:var(--ts-line)]">
      <nav aria-label="Main" className="mx-auto flex h-16 max-w-7xl items-center justify-between px-5 sm:px-8">
        <a href="#top" className="flex items-center gap-2.5 text-[1.0625rem] font-semibold tracking-[-0.01em]">
          <Mark />
          Tessera
        </a>
        <ul className="hidden items-center gap-8 text-[0.9375rem] text-[color:var(--ts-dim)] md:flex">
          {NAV.map((n) => (
            <li key={n.href}>
              <a href={n.href} className="transition-colors duration-150 hover:text-[color:var(--ts-fg)]">
                {n.label}
              </a>
            </li>
          ))}
        </ul>
        <div className="hidden items-center gap-5 md:flex">
          <a href="#" className="text-[0.9375rem] text-[color:var(--ts-dim)] transition-colors duration-150 hover:text-[color:var(--ts-fg)]">
            Log in
          </a>
          <a href="#pricing" className="ts-primary rounded-full px-4 py-2 text-[0.875rem] font-medium">
            Open an account
          </a>
        </div>
        <details className="relative md:hidden">
          <summary className="flex size-10 cursor-pointer items-center justify-center rounded-full" aria-label="Menu">
            <svg viewBox="0 0 24 24" aria-hidden="true" className="size-5">
              <path d="M4 8h16M4 16h16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
            </svg>
          </summary>
          <div className="ts-surface absolute right-0 mt-2 w-60 rounded-2xl p-2">
            {NAV.map((n) => (
              <a key={n.href} href={n.href} className="block rounded-xl px-3 py-2.5 text-[0.9375rem] text-[color:var(--ts-dim)] hover:text-[color:var(--ts-fg)]">
                {n.label}
              </a>
            ))}
            <a href="#pricing" className="ts-primary mt-2 block rounded-xl px-3 py-2.5 text-center text-[0.9375rem] font-medium">
              Open an account
            </a>
          </div>
        </details>
      </nav>
    </header>
  );
}

/* ---- hero ------------------------------------------------------------ */

const NINETY_DAYS = [1.62, 1.66, 1.64, 1.71, 1.78, 1.74, 1.82, 1.9, 1.86, 1.95, 2.02, 1.98, 2.1, 2.18, 2.14, 2.26, 2.31, 2.28, 2.36, 2.48];

function ProductStack() {
  return (
    <div className="relative mx-auto w-full max-w-[34rem] lg:mx-0">
      {/* Back: the balance across every account. */}
      <div className="ts-surface rounded-3xl p-6 pb-10">
        <div className="flex items-center justify-between">
          <p className="text-[0.8125rem] text-[color:var(--ts-faint)]">Cash · all accounts</p>
          <Chip tone="green">+4.2% this month</Chip>
        </div>
        <p className="mt-2 text-[clamp(1.75rem,1.4rem+1.4vw,2.25rem)] font-semibold tracking-[-0.03em]">
          <Money value={2480193.62} />
        </p>
        <Spark points={NINETY_DAYS} className="mt-4 h-16 w-full" />
        <ul className="mt-5 flex flex-col divide-y divide-[color:var(--ts-line)] text-[0.875rem]">
          {[
            ["Operating", "4821", 1204118.4],
            ["Payroll", "0913", 640075.22],
            ["Reserve", "7730", 636000.0],
          ].map(([name, last, amt]) => (
            <li key={name as string} className="flex items-center justify-between py-2.5">
              <span>
                {name} <span className="text-[color:var(--ts-faint)]">••{last}</span>
              </span>
              <Money value={amt as number} className="text-[color:var(--ts-dim)]" />
            </li>
          ))}
        </ul>
      </div>

      {/* Front: a card issued a minute ago, and an invoice matched on
          arrival — pulled up by exactly the balance's bottom padding, so
          they stand on its edge and never over a row of it. */}
      {/* On a phone the invoice takes its own line under the card. */}
      <div className="relative -mt-10 -mr-[3%] -ml-[4%] flex flex-wrap items-end justify-between gap-x-4 gap-y-3 sm:-mr-[6%] sm:-ml-[8%] sm:flex-nowrap">
        <div className="ts-lifted flex aspect-[1.586] w-[46%] shrink-0 flex-col justify-between rounded-2xl bg-[color:var(--ts-fg)] p-4 text-[color:var(--ts-ink)]">
          <div className="flex items-start justify-between">
            <span className="text-[0.75rem] font-semibold">Tessera</span>
            <span aria-hidden="true" className="size-4 rounded-[5px] bg-[color:var(--ts-gold-fill)]" />
          </div>
          <div>
            <p className="font-mono text-[0.75rem] tracking-[0.12em] opacity-80">•••• 2231</p>
            <p className="mt-0.5 text-[0.6875rem] opacity-70">Maya Lin · $5,000 / mo</p>
          </div>
        </div>
        <div className="ts-lifted min-w-0 flex-1 basis-full rounded-2xl bg-[color:var(--ts-surface)] p-4 sm:mb-5 sm:basis-0">
          <div className="flex items-center gap-3">
            <span aria-hidden="true" className="grid grid-cols-1 size-8 shrink-0 place-items-center rounded-full bg-[color:var(--ts-green-soft)] text-[color:var(--ts-green)]">
              <svg viewBox="0 0 16 16" className="size-4">
                <path d="m3.5 8.5 3 3 6-7" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </span>
            <div className="min-w-0">
              <p className="truncate text-[0.8125rem] font-semibold">INV-1042 paid</p>
              <p className="truncate text-[0.75rem] text-[color:var(--ts-faint)]">Northwind Studio · matched</p>
            </div>
            <Money value={12400} className="ml-auto shrink-0 text-[0.875rem] font-semibold" />
          </div>
        </div>
      </div>
    </div>
  );
}

function Hero() {
  return (
    <section id="top" aria-labelledby="hero-title" className="relative isolate overflow-hidden">
      <div aria-hidden="true" className="absolute inset-0 -z-10">
        <AmberGlowField guardSelector="#tessera-hero-copy" />
      </div>
      <div className="mx-auto grid grid-cols-1 max-w-7xl items-center gap-16 px-5 pt-[clamp(4rem,10vh,7rem)] pb-[clamp(4rem,10vh,6rem)] sm:px-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
        <div id="tessera-hero-copy" className="max-w-xl">
          <a href="#tour" className="group inline-flex items-center gap-2 text-[0.875rem] text-[color:var(--ts-dim)]">
            <span className="font-medium text-[color:var(--ts-gold)]">New</span>
            Close the month in a day
            <span aria-hidden="true" className="transition-transform duration-150 group-hover:translate-x-0.5">
              →
            </span>
          </a>
          <h1 id="hero-title" className="mt-6 text-[clamp(2.75rem,1.6rem+4vw,4.75rem)] leading-[0.98] font-semibold tracking-[-0.045em] text-balance">
            Every account, card and invoice. One ledger.
          </h1>
          <p className="mt-6 max-w-[44ch] text-[clamp(1.0625rem,1rem+0.3vw,1.25rem)] leading-[1.55] text-pretty text-[color:var(--ts-dim)]">
            Tessera connects your banks, issues your cards and sends your invoices — then reconciles all of it as it
            happens, so the month closes in a day, not a week.
          </p>
          <div className="mt-9 flex flex-wrap items-center gap-x-6 gap-y-3">
            <a href="#pricing" className="ts-primary rounded-full px-6 py-3 text-[0.9375rem] font-medium">
              Open an account
            </a>
            <a href="#tour" className="group inline-flex items-center gap-1.5 text-[0.9375rem] font-medium">
              Take the tour
              <span aria-hidden="true" className="text-[color:var(--ts-faint)] transition-transform duration-150 group-hover:translate-x-0.5">
                →
              </span>
            </a>
          </div>
          <p className="mt-8 text-[0.875rem] text-[color:var(--ts-faint)]">Free to start · set up in ten minutes · no card required</p>
        </div>
        <ProductStack />
      </div>
    </section>
  );
}

/* ---- tour ------------------------------------------------------------ */

function Tour() {
  return (
    <section id="tour" aria-labelledby="tour-title" className="scroll-mt-20 border-t border-[color:var(--ts-line)]">
      <div className="mx-auto max-w-7xl px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8">
        <div className="max-w-2xl">
          <Label>The product</Label>
          <Title id="tour-title">One place for the money moving through your company.</Title>
        </div>
        <div className="mt-12">
          <TesseraTour />
        </div>
      </div>
    </section>
  );
}

/* ---- features -------------------------------------------------------- */

function Cell({ title, body, children }: { title: string; body: string; children: ReactNode }) {
  return (
    <li className="flex flex-col rounded-3xl bg-[color:var(--ts-surface-2)] p-6 shadow-[inset_0_0_0_1px_var(--ts-line)] sm:p-7">
      <div className="flex min-h-[10.5rem] items-center justify-center" aria-hidden="true">
        {children}
      </div>
      <h3 className="mt-6 text-[1.0625rem] font-semibold tracking-[-0.01em]">{title}</h3>
      <p className="mt-1.5 text-[0.9375rem] leading-[1.6] text-[color:var(--ts-dim)]">{body}</p>
    </li>
  );
}

function Features() {
  return (
    <section id="features" aria-labelledby="features-title" className="scroll-mt-20 border-t border-[color:var(--ts-line)]">
      <div className="mx-auto max-w-7xl px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8">
        <div className="max-w-2xl">
          <Label>Built in</Label>
          <Title id="features-title">The controls a finance team would set up, already set up.</Title>
        </div>
        <ul className="mt-14 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
          <Cell title="Approvals" body="Spend over a threshold asks the right person first, from wherever they are.">
            <div className="ts-surface w-full max-w-[15rem] rounded-2xl p-4">
              <div className="flex items-center gap-2.5">
                <Avatar name="Omar Haddad" />
                <p className="text-[0.8125rem]">
                  <span className="font-medium">Omar</span> <span className="text-[color:var(--ts-dim)]">requests</span>
                </p>
              </div>
              <p className="mt-3 flex items-baseline justify-between text-[0.875rem]">
                <span>Design tool seats</span>
                <Money value={540} className="font-semibold" />
              </p>
              <div className="mt-3 grid grid-cols-2 gap-2 text-[0.75rem] font-medium">
                <span className="rounded-lg py-1.5 text-center shadow-[inset_0_0_0_1px_var(--ts-line-strong)]">Decline</span>
                <span className="rounded-lg bg-[color:var(--ts-fg)] py-1.5 text-center text-[color:var(--ts-ink)]">Approve</span>
              </div>
            </div>
          </Cell>
          <Cell title="Receipts that find their spend" body="Snap it, forward it or let the email arrive — it's matched to the right transaction.">
            <div className="relative">
              <div className="ts-surface w-40 rotate-[-4deg] rounded-xl p-3 text-[0.6875rem] text-[color:var(--ts-faint)]">
                <p className="font-semibold text-[color:var(--ts-fg)]">Umber Coffee</p>
                <div className="mt-2 space-y-1">
                  <p className="flex justify-between"><span>2 × flat white</span><span className="tabular-nums">9.00</span></p>
                  <p className="flex justify-between"><span>Pastry</span><span className="tabular-nums">4.50</span></p>
                  <p className="flex justify-between border-t border-dashed border-[color:var(--ts-line-strong)] pt-1 font-semibold text-[color:var(--ts-fg)]"><span>Total</span><span className="tabular-nums">13.50</span></p>
                </div>
              </div>
              <span className="absolute -right-10 -bottom-3">
                <Chip tone="green">Matched</Chip>
              </span>
            </div>
          </Cell>
          <Cell title="A forecast you can read" body="Runway from what's actually in the bank and what's actually on the way.">
            <div className="w-full max-w-[16rem]">
              <p className="flex items-baseline justify-between text-[0.8125rem]">
                <span className="text-[color:var(--ts-dim)]">Runway</span>
                <span className="text-[1.125rem] font-semibold">26 months</span>
              </p>
              <Forecast past={[3.1, 2.9, 3.0, 2.8, 2.7, 2.72, 2.6]} ahead={[2.55, 2.5, 2.46, 2.4, 2.38]} className="mt-3 h-20 w-full" />
            </div>
          </Cell>
          <Cell title="More than one company" body="Run each entity on its own books and see them together in one balance.">
            <div className="flex w-full max-w-[15rem] flex-col gap-2 text-[0.8125rem]">
              {[
                ["Tessera Inc.", "US", 1860000],
                ["Tessera Ltd", "UK", 420193.62],
                ["Tessera GmbH", "DE", 200000],
              ].map(([n, c, v]) => (
                <div key={n as string} className="ts-surface flex items-center justify-between rounded-xl px-3 py-2">
                  <span>
                    {n} <span className="text-[color:var(--ts-faint)]">{c}</span>
                  </span>
                  <Money value={v as number} className="text-[color:var(--ts-dim)]" />
                </div>
              ))}
            </div>
          </Cell>
          <Cell title="Permissions by role" body="Give your bookkeeper the books, your team their cards, and nobody the keys by accident.">
            <ul className="ts-surface w-full max-w-[15rem] divide-y divide-[color:var(--ts-line)] rounded-2xl text-[0.8125rem]">
              {[
                ["Owner", "Everything"],
                ["Bookkeeper", "Books, read-only bank"],
                ["Employee", "Their own card"],
              ].map(([role, can]) => (
                <li key={role} className="flex items-center justify-between gap-3 px-4 py-2.5">
                  <span className="font-medium">{role}</span>
                  <span className="text-right text-[color:var(--ts-faint)]">{can}</span>
                </li>
              ))}
            </ul>
          </Cell>
          <Cell title="Plays well with your stack" body="Sync to your accounting system, your payroll and the spreadsheet you won't give up.">
            <div className="flex max-w-[15rem] flex-wrap justify-center gap-2">
              {["Accounting", "Payroll", "HR", "CRM", "Spreadsheets", "Webhooks"].map((t) => (
                <Chip key={t}>{t}</Chip>
              ))}
            </div>
          </Cell>
        </ul>
      </div>
    </section>
  );
}

/* ---- a word from a customer (placeholder) ---------------------------- */

function Quote() {
  return (
    <section aria-label="What a customer says" className="border-t border-[color:var(--ts-line)]">
      <figure className="mx-auto max-w-4xl px-5 py-[clamp(6rem,13vw,10rem)] text-center sm:px-8">
        <Mark className="mx-auto size-8" />
        <blockquote className="mt-8 text-[clamp(1.5rem,1.1rem+1.6vw,2.25rem)] leading-[1.3] font-medium tracking-[-0.02em] text-balance">
          &ldquo;We closed June on the second of July. It used to take us until the twentieth — and three people who
          had better things to do.&rdquo;
        </blockquote>
        <figcaption className="mt-8 flex items-center justify-center gap-3 text-[0.9375rem]">
          <Avatar name="Priya Raman" className="size-9" />
          <span className="text-left">
            <span className="block font-medium">Priya Raman</span>
            <span className="block text-[0.8125rem] text-[color:var(--ts-faint)]">Head of Finance, Loom &amp; Loft</span>
          </span>
        </figcaption>
      </figure>
    </section>
  );
}

/* ---- pricing --------------------------------------------------------- */

const PLANS = [
  {
    name: "Starter",
    blurb: "For a company of a few, getting organised.",
    monthly: "$0",
    annual: "$0",
    per: "forever",
    points: ["Business account", "Up to 5 cards", "Invoices and reminders", "Bank feeds and rules"],
    cta: "Open an account",
  },
  {
    name: "Growth",
    blurb: "For a team with a real month end.",
    monthly: "$49",
    annual: "$39",
    per: "per month",
    points: ["Everything in Starter", "Unlimited cards and policies", "Approvals and receipts", "Month-end close", "Accounting sync"],
    cta: "Start a 30-day trial",
    featured: true,
  },
  {
    name: "Scale",
    blurb: "For several entities and an audit.",
    monthly: "Custom",
    annual: "Custom",
    per: "",
    points: ["Everything in Growth", "Multiple entities", "Custom roles", "Dedicated support"],
    cta: "Talk to us",
  },
];

function Pricing() {
  return (
    <section id="pricing" aria-labelledby="pricing-title" className="scroll-mt-20 border-t border-[color:var(--ts-line)]">
      <div className="mx-auto max-w-7xl px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8">
        <div className="flex flex-wrap items-end justify-between gap-8">
          <div className="max-w-2xl">
            <Label>Pricing</Label>
            <Title id="pricing-title">Free to start. Priced by what you use, not who you hire.</Title>
          </div>
          <fieldset className="ts-billing inline-flex rounded-full bg-[color:var(--ts-surface-2)] p-1 text-[0.875rem] font-medium text-[color:var(--ts-dim)] shadow-[inset_0_0_0_1px_var(--ts-line)]">
            <legend className="sr-only">Billing</legend>
            <label className="cursor-pointer rounded-full px-4 py-2">
              <input type="radio" name="tessera-billing" value="monthly" defaultChecked className="sr-only" />
              Monthly
            </label>
            <label className="cursor-pointer rounded-full px-4 py-2">
              <input type="radio" name="tessera-billing" value="annual" className="sr-only" />
              Annual <span className="text-[color:var(--ts-gold)]">−20%</span>
            </label>
          </fieldset>
        </div>
        <ul className="mt-14 grid grid-cols-1 gap-5 lg:grid-cols-3">
          {PLANS.map((p) => (
            <li
              key={p.name}
              className={`flex flex-col rounded-3xl p-7 sm:p-8 ${p.featured ? "ts-lifted bg-[color:var(--ts-surface)]" : "shadow-[inset_0_0_0_1px_var(--ts-line)]"}`}
            >
              <div className="flex items-center justify-between">
                <h3 className="text-[1.0625rem] font-semibold">{p.name}</h3>
                {p.featured && <Chip tone="gold">Most teams</Chip>}
              </div>
              <p className="mt-2 text-[0.9375rem] text-[color:var(--ts-dim)]">{p.blurb}</p>
              <p className="mt-6 flex items-baseline gap-2">
                <span className="text-[2.75rem] leading-none font-semibold tracking-[-0.04em] tabular-nums">
                  <span data-billing="monthly">{p.monthly}</span>
                  <span data-billing="annual">{p.annual}</span>
                </span>
                {p.per && <span className="text-[0.875rem] text-[color:var(--ts-faint)]">{p.per}</span>}
              </p>
              <ul className="mt-8 flex flex-col gap-3 border-t border-[color:var(--ts-line)] pt-7 text-[0.9375rem]">
                {p.points.map((pt) => (
                  <li key={pt} className="flex items-center gap-3">
                    <span aria-hidden="true" className="size-1.5 rounded-[2px] bg-[color:var(--ts-gold-fill)]" />
                    {pt}
                  </li>
                ))}
              </ul>
              <div className="mt-auto pt-10">
                <a
                  href="#"
                  className={`block rounded-full px-5 py-3 text-center text-[0.9375rem] font-medium ${
                    p.featured ? "ts-primary" : "shadow-[inset_0_0_0_1px_var(--ts-line-strong)] transition-colors duration-150 hover:bg-[color:var(--ts-surface)]"
                  }`}
                >
                  {p.cta}
                </a>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

/* ---- FAQ -------------------------------------------------------------- */

const FAQ = [
  { q: "Can I keep my current bank?", a: "Yes. Connect the accounts you already have and Tessera reads them; open accounts with us only if you want to." },
  { q: "How long does setup take?", a: "About ten minutes to connect accounts and issue a first card. Rules and approvals can wait until you need them." },
  { q: "Does it replace my accountant?", a: "No — it gives them clean, matched books to work from, and an export in the format they already use." },
  { q: "What happens at month end?", a: "Most of the close is already done. You get a short checklist of what's left, and one click to post it to your ledger." },
  { q: "Can I cancel any time?", a: "Yes. Monthly plans stop at the end of the month; annual plans at the end of the year. Your data exports either way." },
];

function Faq() {
  return (
    <section id="faq" aria-labelledby="faq-title" className="scroll-mt-20 border-t border-[color:var(--ts-line)]">
      <div className="mx-auto grid grid-cols-1 max-w-7xl gap-12 px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">
        <div>
          <Label>FAQ</Label>
          <Title id="faq-title">The questions finance people ask first.</Title>
        </div>
        <div className="border-t border-[color:var(--ts-line)]">
          {FAQ.map((f) => (
            <details key={f.q} className="border-b border-[color:var(--ts-line)]">
              <summary className="flex cursor-pointer items-center justify-between gap-6 py-5 text-[1.0625rem] font-medium">
                {f.q}
                <svg viewBox="0 0 16 16" aria-hidden="true" className="ts-plus size-4 shrink-0 text-[color:var(--ts-faint)]">
                  <path d="M8 2v12M2 8h12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
                </svg>
              </summary>
              <p className="max-w-[60ch] pb-6 text-[0.9375rem] leading-[1.65] text-[color:var(--ts-dim)]">{f.a}</p>
            </details>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---- close & footer --------------------------------------------------- */

function Close() {
  return (
    <section aria-labelledby="close-title" className="ts-glow border-t border-[color:var(--ts-line)]">
      <div className="mx-auto max-w-3xl px-5 py-[clamp(7rem,15vw,11rem)] text-center sm:px-8">
        <Title id="close-title">Close next month in a day.</Title>
        <p className="mx-auto mt-5 max-w-[40ch] text-[1.0625rem] text-[color:var(--ts-dim)]">
          Open an account in ten minutes. Bring your banks, your team and your invoices — Tessera does the matching.
        </p>
        <div className="mt-9 flex flex-wrap items-center justify-center gap-x-6 gap-y-3">
          <a href="#pricing" className="ts-primary rounded-full px-6 py-3 text-[0.9375rem] font-medium">
            Open an account
          </a>
          <a href="#" className="text-[0.9375rem] font-medium">
            Talk to us
          </a>
        </div>
      </div>
    </section>
  );
}

const FOOTER = [
  { heading: "Product", links: ["Accounts", "Cards", "Invoices", "Close", "Pricing"] },
  { heading: "Company", links: ["About", "Careers", "Press"] },
  { heading: "Resources", links: ["Guides", "Help centre", "Status"] },
  { heading: "Legal", links: ["Terms", "Privacy", "Security"] },
];

function Footer() {
  return (
    <footer className="border-t border-[color:var(--ts-line)]">
      <div className="mx-auto flex max-w-7xl flex-wrap justify-between gap-12 px-5 py-14 sm:px-8">
        <div className="max-w-xs">
          <a href="#top" className="flex items-center gap-2.5 font-semibold">
            <Mark className="size-5" />
            Tessera
          </a>
          <p className="mt-3 text-[0.875rem] text-[color:var(--ts-faint)]">Money, measured — for companies of a few, and of a few hundred.</p>
        </div>
        <div className="flex flex-wrap gap-x-16 gap-y-8">
          {FOOTER.map((col) => (
            <div key={col.heading}>
              <p className="text-[0.8125rem] font-medium">{col.heading}</p>
              <ul className="mt-3 flex flex-col gap-2 text-[0.875rem] text-[color:var(--ts-faint)]">
                {col.links.map((l) => (
                  <li key={l}>
                    <a href="#" className="transition-colors duration-150 hover:text-[color:var(--ts-fg)]">
                      {l}
                    </a>
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </div>
      <p className="mx-auto max-w-7xl px-5 pb-10 text-[0.8125rem] text-[color:var(--ts-faint)] sm:px-8">© 2026 Tessera</p>
    </footer>
  );
}

export function TesseraLanding() {
  return (
    <div className="tessera">
      <Nav />
      <main>
        <Hero />
        <Tour />
        <Features />
        <Quote />
        <Pricing />
        <Faq />
        <Close />
      </main>
      <Footer />
    </div>
  );
}

More landing templates

Start with a background.

The backgrounds are the most finished corner of the catalogue. Take one and drop it under your own copy.

Browse backgrounds