Liana Pepanyan

Pricing Cards

Pricing 73

About

Pricing section for daliagents.com showing the Dali Agents packages - Starter, Agent Care, and Rescue & Migration - as part of the Dali Agents UI.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Pricing Cards to your project and fits it to your theme.

Code

How Pricing Cards is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import React, { useEffect, useRef, useState } from "react";
import { Check } from "lucide-react";

const Pricing = () => {
  const [isAnnual, setIsAnnual] = useState(false);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    const id = requestAnimationFrame(() => setReady(true));
    return () => cancelAnimationFrame(id);
  }, []);

  const plans = [
    {
      name: "Starter",
      price: "Fixed quote",
      description: "Your first working agent, narrow scope",
      features: [
        "One agent wired into one workflow",
        "Acceptance test defined upfront",
        "Build starts after agreement",
        "Doesn't pass? You don't pay",
      ],
      cta: "Start with one agent",
      highlighted: false,
    },
    {
      name: "Agent Care",
      price: isAnnual ? "$9,900" : "$990",
      description: "Retainer for agents already in production",
      features: [
        "Monitoring and alerts",
        "Model and API updates",
        "Monthly improvement cycle",
        "Priority fixes when something breaks",
        "Usage and cost reporting",
        "Scope widens after each workflow passes",
      ],
      cta: "Get Agent Care",
      highlighted: true,
    },
    {
      name: "Rescue & Migration",
      price: "Fixed quote",
      description: "For broken automations and dead stacks",
      features: [
        "Audit of your current automations",
        "Broken flows fixed, not patched",
        "Migration off dead or dying stacks",
        "Workflows keep running during the move",
        "Acceptance test before handover",
        "Quote agreed after a short review",
      ],
      cta: "Book a rescue review",
      highlighted: false,
    },
  ];

  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext("2d");
    if (!canvas || !ctx) return;

    const setSize = () => {
      const rect = canvas.parentElement?.getBoundingClientRect();
      canvas.width = Math.max(1, Math.floor(rect?.width ?? window.innerWidth));
      canvas.height = Math.max(1, Math.floor(rect?.height ?? window.innerHeight));
    };
    setSize();

    type P = { x: number; y: number; v: number; o: number };
    let ps: P[] = [];
    let raf = 0;

    const make = (): P => ({
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      v: Math.random() * 0.25 + 0.05,
      o: Math.random() * 0.35 + 0.15,
    });

    const init = () => {
      ps = [];
      const count = Math.floor((canvas.width * canvas.height) / 12000);
      for (let i = 0; i < count; i++) ps.push(make());
    };

    const draw = () => {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ps.forEach((p) => {
        p.y -= p.v;
        if (p.y < 0) {
          p.x = Math.random() * canvas.width;
          p.y = canvas.height + Math.random() * 40;
          p.v = Math.random() * 0.25 + 0.05;
          p.o = Math.random() * 0.35 + 0.15;
        }
        ctx.fillStyle = `rgba(240,240,242,${p.o})`;
        ctx.fillRect(p.x, p.y, 0.7, 2.2);
      });
      raf = requestAnimationFrame(draw);
    };

    const onResize = () => {
      setSize();
      init();
    };

    const ro = new ResizeObserver(onResize);
    ro.observe(canvas.parentElement || document.body);

    init();
    raf = requestAnimationFrame(draw);

    return () => {
      ro.disconnect();
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <section
      data-locked
      style={
        {
          ["--bg" as any]: "#0b0b0c",
          ["--text" as any]: "#f6f7f8",
          ["--muted" as any]: "#a6a7ac",
          ["--border" as any]: "#2a2a2e",
          ["--card" as any]: "#111214",
          ["--card-muted" as any]: "#0f1012",
          ["--card-pop" as any]: "#15161a",
          ["--accent-line" as any]: "#27272a",
          ["--glow" as any]: "rgba(255,255,255,0.08)",
          ["--btn-primary-bg" as any]: "#f1f1f3",
          ["--btn-primary-fg" as any]: "#0c0c0d",
          ["--btn-ghost-border" as any]: "#2a2a2e",
          ["--btn-ghost-hover" as any]: "rgba(255,255,255,0.04)",
        } as React.CSSProperties
      }
      className={`relative w-full min-h-screen overflow-hidden ${ready ? "is-ready" : ""}`}
    >
      {/* GLOBAL FIXES: kill body margins, paint full-bleed bg, stabilize scrollbar width */}
      <style>{`
        :where(html, body, #__next){
          margin:0; min-height:100%; background:var(--bg); color:var(--text);
          overflow-x:hidden; scrollbar-gutter: stable both-edges;
        }
        /* lock dark visuals regardless of OS theme */
        section[data-locked]{background:var(--bg);color:var(--text);color-scheme:dark}
        @media (prefers-color-scheme: light){section[data-locked]{background:var(--bg);color:var(--text);color-scheme:dark}}
        
        .accent-lines{position:absolute;inset:0;pointer-events:none;opacity:.7}
        .accent-lines .hline,.accent-lines .vline{position:absolute;background:var(--accent-line);animation-fill-mode:forwards}
        .accent-lines .hline{left:0;right:0;height:1px;transform:scaleX(0);transform-origin:50% 50%}
        .accent-lines .vline{top:0;bottom:0;width:1px;transform:scaleY(0);transform-origin:50% 0%}
        .is-ready .accent-lines .hline:nth-of-type(1){top:18%;animation:drawX .6s ease .08s forwards}
        .is-ready .accent-lines .hline:nth-of-type(2){top:50%;animation:drawX .6s ease .16s forwards}
        .is-ready .accent-lines .hline:nth-of-type(3){top:82%;animation:drawX .6s ease .24s forwards}
        .is-ready .accent-lines .vline:nth-of-type(1){left:18%;animation:drawY .7s ease .20s forwards}
        .is-ready .accent-lines .vline:nth-of-type(2){left:50%;animation:drawY .7s ease .28s forwards}
        .is-ready .accent-lines .vline:nth-of-type(3){left:82%;animation:drawY .7s ease .36s forwards}
        @keyframes drawX{to{transform:scaleX(1)}}
        @keyframes drawY{to{transform:scaleY(1)}}
        .kicker,.title,.subtitle{opacity:0;transform:translateY(8px)}
        .is-ready .kicker{animation:kIn .5s ease .08s forwards;letter-spacing:.22em}
        .is-ready .title{animation:tIn .6s cubic-bezier(.22,1,.36,1) .16s forwards}
        .is-ready .subtitle{animation:sIn .6s ease .26s forwards}
        @keyframes kIn{to{opacity:.9;transform:none;letter-spacing:.14em}}
        @keyframes tIn{to{opacity:1;transform:none}}
        @keyframes sIn{to{opacity:1;transform:none}}
        .card{background:var(--card);border:1px solid var(--border);border-radius:16px}
        .card-pop{background:var(--card-pop);border:1px solid var(--border);border-radius:16px;transform:scale(1.02);box-shadow:0 10px 30px rgba(0,0,0,.35);backdrop-filter:blur(6px)}
        .card-muted{background:var(--card-muted)}
        .card-animate{opacity:0;transform:translateY(12px)}
        .is-ready .card-animate{animation:fadeUp .6s ease forwards}
        @keyframes fadeUp{to{opacity:1;transform:translateY(0)}}
        .btn-primary{width:100%;border-radius:12px;padding:10px 16px;font-weight:600;font-size:14px;background:var(--btn-primary-bg);color:var(--btn-primary-fg);transition:transform .15s ease,filter .15s ease,background .2s ease}
        .btn-primary:hover{filter:brightness(.95)}
        .btn-primary:active{transform:translateY(1px)}
        .btn-ghost{width:100%;border-radius:12px;padding:10px 16px;font-weight:600;font-size:14px;color:var(--text);border:1px solid var(--btn-ghost-border);background:transparent;transition:background .2s ease,transform .15s ease}
        .btn-ghost:hover{background:var(--btn-ghost-hover)}
        .btn-ghost:active{transform:translateY(1px)}
        .chip{position:relative;border:1px solid var(--border);background:rgba(17,18,20,.6);color:#e6e7ea;border-radius:9999px;padding:6px 12px;font-size:12px;font-weight:500}
        .chip::before{content:"";position:absolute;inset:0;border-radius:9999px;background:var(--glow);filter:blur(2px)}
        .vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(80% 60% at 50% 12%, rgba(255,255,255,.06), transparent 60%)}
      `}</style>

      <div className="vignette" />

      <div aria-hidden className="accent-lines">
        <div className="hline" />
        <div className="hline" />
        <div className="hline" />
        <div className="vline" />
        <div className="vline" />
        <div className="vline" />
      </div>

      <canvas
        ref={canvasRef}
        className="absolute inset-0 h-full w-full opacity-50 pointer-events-none"
      />

      <div className="relative mt-14">
        <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
          <div className="text-center mx-auto mb-12 max-w-3xl">
            <div className="kicker mb-2 text-xs uppercase tracking-[0.14em]" style={{ color: "#b5b6bb" }}>
              Dali Agents
            </div>
            <h1 className="title mb-4 text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight" style={{ color: "var(--text)" }}>
              Agents that work - or you don't pay
            </h1>
            <p className="subtitle mb-6 text-lg" style={{ color: "var(--muted)" }}>
              Fixed-scope packages with one acceptance test defined upfront. Build starts after agreement.
            </p>

            <div className="subtitle inline-flex items-center rounded-full p-1" style={{ background: "rgba(255,255,255,0.03)" }}>
              <button
                aria-pressed={!isAnnual}
                style={{
                  padding: "10px 20px",
                  borderRadius: 9999,
                  fontSize: 14,
                  fontWeight: 600,
                  background: !isAnnual ? "rgba(255,255,255,0.06)" : "transparent",
                  color: !isAnnual ? "var(--text)" : "#b5b6bb",
                  transition: "background .2s ease, color .2s ease",
                }}
                onClick={() => setIsAnnual(false)}
              >
                Monthly
              </button>
              <button
                aria-pressed={isAnnual}
                style={{
                  padding: "10px 20px",
                  borderRadius: 9999,
                  fontSize: 14,
                  fontWeight: 600,
                  background: isAnnual ? "rgba(255,255,255,0.06)" : "transparent",
                  color: isAnnual ? "var(--text)" : "#b5b6bb",
                  transition: "background .2s ease, color .2s ease",
                }}
                onClick={() => setIsAnnual(true)}
              >
                Annual
              </button>
            </div>
          </div>

          <div className="mx-auto grid max-w-7xl grid-cols-1 gap-6 md:grid-cols-3">
            {plans.map((plan, index) => (
              <div
                key={plan.name}
                className={`card-animate ${plan.highlighted ? "card-pop" : "card"}`}
                style={{ padding: 24, animationDelay: `${0.32 + index * 0.08}s` }}
              >
                {plan.highlighted && (
                  <div className="absolute -top-4 left-1/2 -translate-x-1/2">
                    <div className="relative">
                      <div className="absolute inset-0 rounded-full" style={{ background: "rgba(255,255,255,0.10)", filter: "blur(2px)" }} />
                      <div className="relative chip px-4 py-1.5" style={{ borderRadius: 9999, background: "rgba(20,20,24,0.6)" }}>
                        <div className="relative z-10 flex items-center gap-1.5">
                          <span className="inline-block h-1 w-1 animate-pulse rounded-full" style={{ background: "rgba(255,255,255,0.7)" }} />
                          <span style={{ fontSize: 12, fontWeight: 600, color: "#e6e7ea" }}>Most Popular</span>
                        </div>
                      </div>
                    </div>
                  </div>
                )}

                <div className="mb-6">
                  <h3 className="mb-2 text-xl font-medium" style={{ color: "var(--text)" }}>{plan.name}</h3>
                  <div className="flex items-baseline gap-2">
                    <span className="text-4xl font-bold" style={{ color: "var(--text)" }}>{plan.price}</span>
                    {plan.price !== "Fixed quote" && (
                      <span className="text-sm" style={{ color: "var(--muted)" }}>
                        per {isAnnual ? "year" : "month"}
                      </span>
                    )}
                  </div>
                  <p className="mt-4 text-sm" style={{ color: "var(--muted)" }}>{plan.description}</p>
                </div>

                <div className="mb-6 space-y-3">
                  {plan.features.map((feature, i) => (
                    <div key={i} className="flex items-center gap-2.5">
                      <Check className="h-4 w-4" style={{ color: "#8e9096" }} />
                      <span className="text-sm" style={{ color: "#d4d5d9" }}>{feature}</span>
                    </div>
                  ))}
                </div>

                <button className={plan.highlighted ? "btn-primary" : "btn-ghost"}>
                  {plan.cta}
                </button>
              </div>
            ))}
          </div>

          <a
            href="https://daliagents.com"
            target="_blank"
            rel="noreferrer"
            className="mt-10 block text-center text-[10px] tracking-wide text-zinc-500 transition-colors hover:text-zinc-300"
          >
            daliagents.com
          </a>
        </div>
      </div>
    </section>
  );
};

export default Pricing;

Dependencies

More by Liana Pepanyan

View all
  • CTA with Text Marquee

    CTA with Text Marquee

    1.1K
  • Dock

    Dock

    421
  • Hero Minimalism

    Hero Minimalism

    413
  • CTA with Marquee

    CTA with Marquee

    355
  • Dark Grid

    Dark Grid

    203
  • Processing Card

    Processing Card

    197
  • Login / Signup

    Login / Signup

    194
  • Blueprint Shader Grid

    Blueprint Shader Grid

    136

More in Pricing

View all
  • Pricing Section

    Pricing Section

    582
  • Pricing Section

    Pricing Section

    173
  • Pricing Card

    Pricing Card

    365
  • pricing section

    pricing section

    158
  • Pricing Cards

    Pricing Cards

    86
  • Colorful-Pricing

    Colorful-Pricing

    85
  • Glowing Pricing Tiers

    Glowing Pricing Tiers

    45
  • RuixenPricing05

    RuixenPricing05

    102