Sign up for free here:

Mocha: AI-Powered No‑Code App Builder for Entrepreneurs

Copy the prompt below and paste into Mocha’s chat

# Atoms — Starter Prompt for Mocha

## What to build

Build a full-stack web app called **Atoms**. It is a minimalist bookmark manager designed for people who use LLMs (like ChatGPT, Claude, Gemini). Users save URLs, the app extracts the page content using the **Firecrawl API** (`https://api.firecrawl.dev/v1/scrape`), generates a clean AI-enhanced title and description, fetches the favicon, auto-tags the content, and stores it as a lightweight bookmark card. Users can then select one or multiple bookmarks and copy structured markdown to their clipboard to paste into any LLM as context.

The tagline is: **"Atomic context for your AI conversations."**

---

## Design system

This app must look and feel premium, minimal, and intentional. Think Steph Ango / Obsidian aesthetic. Flat, no shadows, no gradients, no rounded-everything. Sharp, typographic, ink-on-paper.

### Font
- Use **Geist** (by Vercel) for all text. Import from `https://cdn.jsdelivr.net/npm/[email protected]/dist/fonts/geist-sans/style.min.css`
- Use **Geist Mono** for any monospaced elements (tags, metadata, code). Import from `https://cdn.jsdelivr.net/npm/[email protected]/dist/fonts/geist-mono/style.min.css`

### Color scheme — Flexoki
Use the Flexoki color scheme by Steph Ango. Implement both light and dark mode with a toggle.

**Light mode:**
- Background (bg): `#FFFCF0`
- Background secondary (bg-2): `#F2F0E5`
- Borders (ui): `#E6E4D9`
- Borders hover (ui-2): `#DAD8CE`
- Borders active (ui-3): `#CECDC3`
- Faint text (tx-3): `#B7B5AC`
- Muted text (tx-2): `#6F6E69`
- Primary text (tx): `#100F0F`
- Links/active (cyan): `#24837B`
- Tags accent (purple): `#5E409D`
- Error (red): `#AF3029`
- Success (green): `#66800B`
- Warning (orange): `#BC5215`

**Dark mode:**
- Background (bg): `#100F0F`
- Background secondary (bg-2): `#1C1B1A`
- Borders (ui): `#282726`
- Borders hover (ui-2): `#343331`
- Borders active (ui-3): `#403E3C`
- Faint text (tx-3): `#575653`
- Muted text (tx-2): `#878580`
- Primary text (tx): `#CECDC3`
- Links/active (cyan): `#3AA99F`
- Tags accent (purple): `#8B7EC8`
- Error (red): `#D14D41`
- Success (green): `#879A39`
- Warning (orange): `#DA702C`

### UI principles
- No box shadows. Ever.
- No border-radius larger than 4px. Prefer 2px or 0.
- 1px borders only, using the `ui` color.
- Generous whitespace. Let elements breathe.
- Cards should be flat rectangles with a subtle 1px border.
- Hover states: shift border to `ui-2`, do not add shadows or scale transforms.
- Buttons: flat, outlined or filled with `cyan` accent. No pill shapes.
- Icons: use Lucide icons, thin stroke weight.
- The overall feel should be like a beautifully typeset document, not a "web app."

---

## Authentication

- Use email + password auth (Mocha's built-in auth system).
- After signup, user lands on an empty state with a prompt to save their first bookmark.
- Track user's total bookmark count for free tier enforcement.

---

## Database schema

### Users table (extends Mocha auth)
- `id` (primary key)
- `email`
- `plan` (enum: "free" | "pro", default "free")
- `stripe_customer_id` (nullable)
- `stripe_subscription_id` (nullable)
- `bookmark_count` (integer, default 0)
- `created_at`

### Bookmarks table
- `id` (primary key, UUID)
- `user_id` (foreign key)
- `url` (text, the original URL)
- `title` (text, AI-enhanced title)
- `description` (text, AI-enhanced 1-2 sentence description)
- `favicon_url` (text, URL to the site's favicon)
- `content_markdown` (text, the full defuddle-extracted markdown content)
- `word_count` (integer)
- `domain` (text, extracted from URL, e.g. "paulgraham.com")
- `created_at` (datetime)
- `updated_at` (datetime)

### Tags table
- `id` (primary key)
- `user_id` (foreign key)
- `name` (text, the tag label)
- `created_at`
- Unique constraint on (user_id, name)

### Bookmark_Tags join table
- `bookmark_id` (foreign key)
- `tag_id` (foreign key)
- Primary key on (bookmark_id, tag_id)

---

## Core features

### 1. Save a bookmark
- User pastes a URL into an input field at the top of the main view.
- On submit, the backend:
  1. Calls the **Firecrawl API** to scrape the URL and extract clean markdown content. The API call looks like this:
     ```
     POST <https://api.firecrawl.dev/v1/scrape>
     Headers: { "Content-Type": "application/json", "Authorization": "Bearer YOUR_FIRECRAWL_API_KEY" }
     Body: { "url": "THE_URL", "formats": ["markdown"], "onlyMainContent": true }
     ```
     The response returns `data.markdown` (the clean page content), `data.metadata.title`, `data.metadata.description`, `data.metadata.ogImage`, and `data.metadata.sourceURL`.
  2. Sends the raw title and first ~500 words of the extracted markdown to an AI call (OpenAI API) with a prompt like: *"Given this article title and excerpt, generate: 1) A clean, concise title (max 80 chars). Shorter is better. If the page is about one clear thing, just name it simply — don't add flair or clickbait. 'Flexoki Color Scheme' is better than 'A Beautiful Inky Color Scheme for Prose and Code'. 2) A concise 1-2 sentence description of what the reader will learn or find. 3) 3-5 relevant tags as a JSON array of lowercase strings. Return JSON only: {title, description, tags}"*
  3. Stores the bookmark with the AI-enhanced title, description, extracted markdown, favicon URL, and word count (calculated from the markdown).
  4. Creates any new tags that don't already exist for this user, and links them to the bookmark.
- If the user is on the free plan and already has 100 bookmarks, block the save and show an upgrade prompt.
- While processing, show a skeleton card in the feed with a subtle loading animation (a pulsing opacity on the card background, nothing flashy).

### 2. Bookmark feed (main view)
- Default view: a vertical list of bookmark cards, newest first.
- Each card shows:
  - Favicon (16x16 or 20x20, displayed inline left of the domain name)
  - Domain name in muted text (tx-2), Geist Mono
  - AI-enhanced title in primary text (tx), Geist, semi-bold
  - AI-enhanced description in muted text (tx-2), Geist, regular
  - Tags displayed as small flat labels with `purple` text on a subtle purple-tinted background
  - Relative timestamp ("2h ago", "3d ago") in faint text (tx-3)
  - A checkbox on the left side of each card for multi-select (appears on hover, always visible on mobile)
- Clicking a card opens a **center-peek modal** that displays the bookmark in a clean reading view:
  - Modal should be centered, max-width ~640px, with a semi-transparent backdrop.
  - Top of modal: favicon + domain (muted, Geist Mono), then the AI-enhanced title large and bold.
  - Below title: tags as flat labels, date saved, word count — all in a single muted metadata line.
  - Below metadata: the full extracted markdown **rendered as formatted HTML** (headings, paragraphs, lists, code blocks, links — not raw markdown text). Use a markdown-to-HTML renderer.
  - At the top-right of the modal (next to the X close button), include a toggle or button to switch between **"Read"** and **"Edit"** modes. Read mode is the default and shows rendered HTML. Edit mode swaps the content area to a plain textarea/code editor showing the raw markdown, which the user can modify and save. When they save, the updated markdown replaces the stored content and the modal switches back to Read mode.
  - At the bottom of the modal: two buttons — "Open Original" (opens the source URL in a new tab) and "Copy to Clipboard" (copies this single bookmark's structured markdown).
  - Close the modal by clicking the backdrop, pressing Escape, or clicking an X button in the top-right corner.
  - The modal should scroll internally if the content is long. The page behind should not scroll.
- Cards should be compact. No large images. Favicon is the only visual. This keeps the app fast and lightweight.

### 3. Tag sidebar (folders)
- Left sidebar showing all of the user's tags as a flat list.
- Each tag shows its name and bookmark count.
- Clicking a tag filters the main feed to only show bookmarks with that tag.
- "All Atoms" at the top to clear the filter and show everything.
- Tags are the only organizational system. No folders, no collections. Tags ARE the folders.
- Sidebar should be collapsible on mobile (hamburger menu).

### 4. Multi-select and copy to clipboard
- When one or more bookmarks are selected via checkbox, a floating action bar appears at the bottom of the screen.
- The action bar shows: "[X] selected" count, a "Copy to Clipboard" button, and a "Deselect All" button.
- The "Copy to Clipboard" button copies a structured markdown block for ALL selected bookmarks to the clipboard. Format for each bookmark:


[Title]

Source: [URL] Domain: [domain] | Saved: [date] Tags: [tag1, tag2, tag3]

[Full markdown content extracted by Firecrawl]



- If multiple bookmarks are selected, concatenate them with a blank line between each block.
- After copying, show a brief toast notification: "Copied [X] atoms to clipboard" using the success (green) color.
- This is the killer feature. The copied output should be clean, well-structured markdown that any LLM can parse perfectly.

### 5. Search
- A search input at the top of the feed.
- Searches across: title, description, domain, tags, and the full markdown content.
- Results update as the user types (debounced, 300ms).
- Highlight matching terms is optional but nice to have.

### 6. Delete bookmarks
- Each card has a small trash icon (Lucide `Trash2`) that appears on hover.
- Clicking it shows a confirmation: "Delete this atom?" with Cancel and Delete buttons.
- Multi-select delete: when items are selected, the floating action bar also shows a "Delete" button.
- Deleting decrements the user's `bookmark_count`.

---

## Stripe subscription (payments)

### Pricing
- **Free tier:** First 100 bookmarks. All features included.
- **Pro tier:** Unlimited bookmarks. $10/month.

### Implementation
- Integrate Stripe Checkout for subscription.
- Store the Stripe `customer_id` and `subscription_id` on the user record.
- When a free user hits 100 bookmarks and tries to save another, show a clean modal:
  - Heading: "You've saved 100 atoms"
  - Body: "Upgrade to Pro for unlimited bookmarks. $10/month. Cancel anytime."
  - Button: "Upgrade to Pro" → redirects to Stripe Checkout
  - Link below: "Maybe later" to dismiss
- After successful checkout, Stripe webhook updates the user's plan to "pro".
- Add a "Settings" page accessible from the sidebar where users can:
  - See their current plan and bookmark count
  - Manage their subscription (link to Stripe Customer Portal)
  - Toggle dark/light mode

### Stripe webhook events to handle
- `checkout.session.completed` → set user plan to "pro"
- `customer.subscription.deleted` → set user plan to "free"
- `customer.subscription.updated` → handle plan changes

---

## Pages and routing

1. **`/` (Landing page)** — public, unauthenticated. Clean marketing page explaining Atoms. Show the tagline, a screenshot/mockup of the app, the pricing (Free: 100 atoms, Pro: unlimited for $10/mo), and sign-up CTA. Keep it minimal — one page, no scroll hijacking, no animations. Just text, maybe one illustration.
2. **`/app` (Main app)** — authenticated. The bookmark feed with tag sidebar, search, and multi-select. This is where users spend all their time.
3. **`/settings`** — authenticated. Account settings, plan info, Stripe management, dark/light mode toggle.
4. **`/login`** and **`/signup`** — auth pages, minimal.

---

## API endpoints (Hono)

- `POST /api/bookmarks` — save a new bookmark (accepts `{url}`, calls Firecrawl to scrape, then OpenAI for title/description/tags)
- `GET /api/bookmarks` — list user's bookmarks (supports `?tag=`, `?search=`, `?page=`)
- `DELETE /api/bookmarks/:id` — delete a bookmark
- `GET /api/tags` — list user's tags with counts
- `POST /api/stripe/checkout` — create a Stripe Checkout session
- `POST /api/stripe/webhook` — handle Stripe webhook events
- `GET /api/stripe/portal` — create a Stripe Customer Portal session

---

## Technical notes

- Use the **Firecrawl API** (`https://api.firecrawl.dev/v1/scrape`) for all content extraction. It returns clean markdown with `onlyMainContent: true`. No npm packages needed for scraping — it's just a single fetch/POST call. I will provide my Firecrawl API key as a secret.
- For the AI title/description/tag generation, use the OpenAI API. I will provide my own API key as a secret. Use `gpt-4o-mini` model to keep costs low.
- For favicon extraction, use `https://www.google.com/s2/favicons?domain=[domain]&sz=64` as a reliable source. Just store this URL string — don't download the image.
- Rate limit the bookmark save endpoint to prevent abuse (max 10 saves per minute per user).
- Paginate the bookmark feed (20 per page, infinite scroll).
- Store the full extracted markdown in the database but only display the title + description in the feed. The full markdown is used for search and for the copy-to-clipboard output.

---

## Empty states

- **No bookmarks yet:** Center of feed shows: "No atoms yet. Paste a URL above to save your first." in muted text.
- **No search results:** "No atoms match your search." in muted text.
- **No bookmarks for tag:** "No atoms tagged [tag name]." in muted text.

---

## Mobile responsiveness

- Sidebar collapses to a hamburger menu.
- Bookmark cards stack full-width.
- Floating action bar stays pinned to bottom.
- URL input stays at top.
- Checkboxes always visible (no hover state on mobile).

---

Build this app step by step, starting with the database schema and auth, then the core bookmark save + feed functionality, then the multi-select + copy feature, then Stripe integration, and finally the landing page.