<aside>

I replaced a paid SaaS tool with a self-built component - using AI as my engineering team

I replaced a $30/month SaaS subscription with a custom-built testimonial wall using Figma Make AI, Supabase, and Claude.

The entire build took 4 hours.

👉 See it live: Darpan Dadhaniya | Senior Product Leader

20260329-2038-47.9536241.gif

</aside>

<aside>

The problem

Testimonial SaaS is a solid product.

But three things bothered me:

I wanted full control over design, data, and hosting.

Testimonial SaaS Self-built
Monthly cost ~$30 USD $0 (Supabase + Figma free tiers)
Annual cost ~$360 USD $0
Design control Theme picker, colour swatches Full custom CSS, any layout
Data ownership Their Firebase (vendor lock-in) My Supabase (portable Postgres)
Avatar hosting Firebase URLs (die on cancel) Supabase Storage (permanent)
Embed fit Visible iframe seam on dark pages Seamless #191919 background match
Update workflow Dashboard UI Edit JSON in database
</aside>

<aside>

Architecture decisions

Before opening Figma Make, I spent time with Claude planning the system architecture.

The goal was to identify the simplest stack to provide a dynamic, data-driven component I could embed in Notion.

flowchart LR
    subgraph Claude["Claude - Architect"]
        C1["Architecture planning"]
        C2["SQL scripts"]
        C3["Prompt engineering"]
        C4["Code review"]
    end

    subgraph Supabase["Supabase - Backend"]
        S1["Storage bucket <br> (avatar images)"]
        S2["KV store <br> (testimonial JSON)"]
        S3["Edge function <br> (Deno API)"]
    end

    subgraph FigmaMake["Figma Make AI - Frontend"]
        F1["React + Tailwind"]
        F2["Masonry grid"]
        F3["Scroll animation"]
    end

    subgraph Notion["Notion - Host"]
        N1["Dark mode portfolio"]
    end

    C1 -->|"Plans stack"| Supabase
    C3 -->|"Drafts prompts"| FigmaMake
    C4 -->|"Reviews output"| FigmaMake
    C2 -->|"Writes setup"| Supabase

    S1 -->|"Permanent avatar URLs"| S3
    S2 -->|"JSON array"| S3
    S3 -->|"REST API"| F1
    F1 --> F2
    F2 --> F3
    F3 -->|"Published embed"| N1

Tech stack

Key tradeoffs

GitHub Repo

https://github.com/darpandadhaniya/wall-of-love

</aside>

<aside>

Build log: Prompts and Iterations

This section shows the actual AI prompts I used, what they produced, and how I refined the output. Each prompt demonstrates a specific aspect of working with AI effectively: providing context, decomposing complexity, and iterating based on visual feedback.

Step 1: Context brief

Before any design work, I gave Figma Make a project brief. This front-loads the constraints so every subsequent prompt inherits the same design system.

I'm building a testimonial "Wall of Love" component inspired by Testimonial SaaS's masonry animated format. It will be embedded in a Notion portfolio page forced to dark mode (background #191919).

The wall is a responsive masonry grid of testimonial cards that scroll vertically in a continuous, very slow animation. Columns scroll in alternating directions - up, down, up, down. Animation pauses on hover.

Card design: circular avatar, name, optional job title, 5 orange stars, body text, "Show more" link for long reviews. Cards have a coloured left border accent and solid orange offset shadow.

Constraints: dark mode only, #191919 background, 100% width, 650px fixed height, responsive 4→3→2→1 columns, very slow scroll (~60s per cycle), no headers or branding.

Why this works: The brief establishes the target (Testimonial SaaS's format), the constraint (Notion dark mode), the card anatomy, and the responsive behaviour - all in one message. Every follow-up prompt can focus on one detail without re-establishing the full context.

Step 2: The card

Build a single testimonial card matching the attached screenshot and the design specs in the context above. Make two versions side by side: one with a short testimonial (2 lines) and one with a long testimonial (7+ lines that truncates with "Show more").

image.png

What I refined: The initial output used a soft glow shadow. My Testimonial SaaS reference uses a solid hard-edge offset shadow. I followed up with: "Replace any soft glow or blur shadow with a solid #FFA500 orange block offset 4px right and 4px down. No blur, no transparency - a clean hard-edge offset shadow."

The lesson: AI tools default to conventional design patterns (soft shadows). Reference images and specific language ("hard-edge", "no blur") override those defaults.

Step 3: The scrolling column

Stack 8 of these cards in a single vertical column with a 12px gap. Mix short, medium, and long testimonials. Duplicate the full stack underneath for a seamless scroll loop. Animate the column scrolling upward - very slow, 60 seconds per full cycle, linear timing. Pause on hover. Wrap in a 650px container with overflow hidden and #191919 background.

Key technical detail: The infinite scroll works by duplicating the card stack. The CSS animation moves the entire column upward by exactly 50% (the height of one complete set), then instantly resets to 0. Because both halves are identical, the reset is invisible. The speed is calculated dynamically: duration = (container height / 2) / 30px per second. This keeps the scroll speed consistent regardless of content length.

20260329-2029-35.6188754.gif

Step 4: The responsive grid

Place multiple scrolling columns side by side. Use CSS grid with auto-fill and a minimum card width so the column count adapts to screen width. Columns 1 and 3 scroll up, columns 2 and 4 scroll down. The horizontal spacing between columns should be fluid - distribute remaining space evenly.

20260329-2031-42.1083580.gif

What I refined: The first draft used fixed card widths that didn't match Testimonial SaaS's fluid behaviour. Comparing screenshots side by side, I noticed Testimonial SaaS keeps card width constant and varies the horizontal gap. The fix: justify-content: space-evenly with fixed column widths, letting the gaps absorb the extra space.

Step 5: Whole bunch more iterations + Testing + Connecting databases and GitHub

Step 6: Notion embed polish

Add 60px gradient fade masks at top and bottom (#191919 to transparent). Background must be exactly #191919 with zero outer margin, padding, or border. The component edges must be invisible inside the Notion embed iframe.

Why #191919 matters: Notion's dark mode background is exactly #191919. Even one shade off (#1A1A1A or #181818) creates a visible line where the embed iframe meets the page. I confirmed this hex value through documentation and testing before committing to it.

20260329-2036-16.4283919.gif

</aside>

<aside>

</aside>

<aside>

</aside>

<aside>

</aside>