Skip to content

Commit dbddae3

Browse files
Sbussisoclaude
andcommitted
feat(pricing): remove duplicate plan-detail cards, let Clerk render the features
The detail cards I added in the previous commit duplicated content the Clerk <PricingTable /> already renders — Clerk pulls each plan's feature list directly from the dashboard config (name + description per feature) and draws it inside every card. Showing the same information twice, in different layouts, noised up the page and created two places to maintain the tier story instead of one. Removed: - The three-card "What each plan buys you" grid (pricing-detail-grid) - The full block of pricing-detail-* CSS (~160 lines of now-unreferenced class rules, the gradient border on "Pro", the "MOST POPULAR" badge, all six subsections on each card) Kept: - "Need higher caps?" link — valuable, doesn't fit on a Clerk card, now rendered as a simple muted note directly below the PricingTable - "How usage-based pricing works" 6-card grid — still useful because it explains the *pricing model* itself (viewer-hours, recordings unmetered, no overage billing), not what each tier includes - FAQ section — unchanged The Clerk dashboard is now the single source of truth for per-plan feature lists. To update what visitors see, the operator edits the feature set on each plan in the Clerk dashboard; no code change here is needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 699f9a2 commit dbddae3

2 files changed

Lines changed: 13 additions & 310 deletions

File tree

frontend/src/index.css

Lines changed: 4 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,180 +2383,17 @@ body {
23832383
margin-bottom: 4rem;
23842384
}
23852385

2386-
/* ── Detailed plan-comparison cards ─────────────────────────────── */
2387-
/* Sits below the Clerk-rendered PricingTable; authoritative source
2388-
of "what each tier buys" so visitors don't depend on the Clerk
2389-
dashboard's feature list to understand the plans. */
2390-
2391-
.pricing-detail {
2386+
/* Small "need higher caps?" note below the Clerk PricingTable. */
2387+
.pricing-detail-footnote {
23922388
position: relative;
23932389
z-index: 1;
2394-
max-width: 1200px;
2395-
margin: 0 auto 4rem;
2396-
padding: 0 1.5rem;
2397-
}
2398-
2399-
.pricing-detail-title {
2400-
font-size: clamp(1.75rem, 3.5vw, 2.25rem);
2401-
text-align: center;
2402-
margin-bottom: 0.5rem;
2403-
letter-spacing: -0.015em;
2404-
}
2405-
2406-
.pricing-detail-intro {
2407-
text-align: center;
2408-
color: var(--text-secondary);
2409-
max-width: 40rem;
2410-
margin: 0 auto 2.5rem;
2411-
line-height: 1.55;
2412-
}
2413-
2414-
.pricing-detail-grid {
2415-
display: grid;
2416-
grid-template-columns: repeat(3, 1fr);
2417-
gap: 1.25rem;
2418-
align-items: stretch;
2419-
}
2420-
2421-
@media (max-width: 960px) {
2422-
.pricing-detail-grid { grid-template-columns: 1fr; }
2423-
}
2424-
2425-
.pricing-detail-card {
2426-
position: relative;
2427-
background: var(--bg-card, rgba(255, 255, 255, 0.04));
2428-
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
2429-
border-radius: 14px;
2430-
padding: 1.75rem 1.5rem 1.25rem;
2431-
display: flex;
2432-
flex-direction: column;
2433-
gap: 1.25rem;
2434-
}
2435-
2436-
.pricing-detail-card.pricing-detail-featured {
2437-
border-color: rgba(34, 197, 94, 0.45);
2438-
background: linear-gradient(180deg, rgba(34, 197, 94, 0.07), rgba(34, 197, 94, 0.01));
2439-
box-shadow: 0 18px 60px -20px rgba(34, 197, 94, 0.28);
2440-
}
2441-
2442-
.pricing-detail-featured-badge {
2443-
position: absolute;
2444-
top: -12px;
2445-
right: 16px;
2446-
background: linear-gradient(135deg, #22c55e, #16a34a);
2447-
color: #03110a;
2448-
font-size: 0.7rem;
2449-
font-weight: 700;
2450-
letter-spacing: 0.1em;
2451-
text-transform: uppercase;
2452-
padding: 0.3rem 0.7rem;
2453-
border-radius: 999px;
2454-
}
2455-
2456-
.pricing-detail-card-head h3 {
2457-
font-size: 1.4rem;
2458-
margin: 0 0 0.35rem;
2459-
}
2460-
2461-
.pricing-detail-price {
2462-
font-size: 2rem;
2463-
font-weight: 700;
2464-
color: var(--text-primary);
2465-
line-height: 1;
2466-
margin-bottom: 0.5rem;
2467-
}
2468-
2469-
.pricing-detail-price span {
2470-
font-size: 1rem;
2471-
font-weight: 400;
2472-
color: var(--text-muted);
2473-
margin-left: 0.25rem;
2474-
}
2475-
2476-
.pricing-detail-annual {
2477-
font-size: 0.75rem;
2478-
font-weight: 400;
2479-
color: var(--text-secondary);
2480-
margin-top: 0.3rem;
2481-
letter-spacing: 0;
2482-
}
2483-
2484-
.pricing-detail-for {
2485-
color: var(--text-secondary);
2486-
font-size: 0.9rem;
2487-
line-height: 1.45;
2488-
margin: 0;
2489-
}
2490-
2491-
.pricing-detail-section {
2492-
border-top: 1px solid rgba(255, 255, 255, 0.06);
2493-
padding-top: 0.9rem;
2494-
}
2495-
2496-
.pricing-detail-section-label {
2497-
color: var(--accent-green, #22c55e);
2498-
font-size: 0.7rem;
2499-
font-weight: 700;
2500-
letter-spacing: 0.12em;
2501-
text-transform: uppercase;
2502-
margin-bottom: 0.55rem;
2503-
}
2504-
2505-
.pricing-detail-card.pricing-detail-free .pricing-detail-section-label {
2506-
color: #93c5fd;
2507-
}
2508-
2509-
.pricing-detail-card.pricing-detail-pro-plus .pricing-detail-section-label {
2510-
color: #c4b5fd;
2511-
}
2512-
2513-
.pricing-detail-section ul {
2514-
list-style: none;
2515-
padding: 0;
2516-
margin: 0;
2517-
}
2518-
2519-
.pricing-detail-section li {
2520-
position: relative;
2521-
padding-left: 1.2rem;
2522-
margin-bottom: 0.45rem;
2523-
color: var(--text-secondary);
2524-
font-size: 0.88rem;
2525-
line-height: 1.45;
2526-
}
2527-
2528-
.pricing-detail-section li::before {
2529-
content: "✓";
2530-
position: absolute;
2531-
left: 0;
2532-
top: 0;
2533-
color: var(--accent-green, #22c55e);
2534-
font-weight: 700;
2535-
}
2536-
2537-
.pricing-detail-section li strong {
2538-
color: var(--text-primary);
2539-
font-weight: 600;
2540-
}
2541-
2542-
.pricing-detail-missing li {
2543-
color: var(--text-muted);
2544-
}
2545-
2546-
.pricing-detail-missing li::before {
2547-
content: "—";
2548-
color: var(--text-muted);
2549-
}
2550-
2551-
.pricing-detail-footnote {
25522390
text-align: center;
25532391
color: var(--text-muted);
25542392
font-size: 0.85rem;
25552393
line-height: 1.55;
2556-
margin-top: 2rem;
2394+
margin: 0 auto 3rem;
25572395
max-width: 44rem;
2558-
margin-left: auto;
2559-
margin-right: auto;
2396+
padding: 0 1.5rem;
25602397
}
25612398

25622399
.pricing-detail-footnote a {

frontend/src/pages/PricingPage.jsx

Lines changed: 9 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -23,149 +23,15 @@ function PricingPage() {
2323
<PricingTable for="organization" />
2424
</div>
2525

26-
{/* Detailed plan comparison — the Clerk PricingTable above only
27-
shows what the operator has configured as each plan's feature
28-
list in the Clerk dashboard. This block is the source of truth
29-
for what each tier actually buys, organised so a scanning
30-
visitor can find their use case without reading the whole page.
31-
If you change a number here, also change it in /docs#plans and
32-
in backend/app/core/plans.py::PLAN_LIMITS. */}
33-
<div className="pricing-detail">
34-
<h2 className="pricing-detail-title">What each plan buys you</h2>
35-
<p className="pricing-detail-intro">
36-
Below is the complete, per-tier breakdown. Everything on this
37-
page is enforced in code — our open-source repos are the source
38-
of truth, and the numbers match exactly.
39-
</p>
40-
41-
<div className="pricing-detail-grid">
42-
43-
{/* Free — designed to be usable long-term for home users,
44-
not a hobbled trial. The "for who" lines are important;
45-
they let visitors self-identify without reading numbers. */}
46-
<div className="pricing-detail-card pricing-detail-free">
47-
<div className="pricing-detail-card-head">
48-
<h3>Free</h3>
49-
<div className="pricing-detail-price">$0<span>/mo</span></div>
50-
<p className="pricing-detail-for">For home users with a few cameras they check occasionally.</p>
51-
</div>
52-
53-
<div className="pricing-detail-section">
54-
<div className="pricing-detail-section-label">Usage</div>
55-
<ul>
56-
<li><strong>30 viewer-hours / month</strong> of live playback</li>
57-
<li>Up to <strong>5 cameras</strong> across <strong>2 CloudNodes</strong></li>
58-
<li><strong>2 team seats</strong></li>
59-
<li>10 concurrent live-dashboard connections</li>
60-
</ul>
61-
</div>
62-
63-
<div className="pricing-detail-section">
64-
<div className="pricing-detail-section-label">Included</div>
65-
<ul>
66-
<li>Live HLS streaming + snapshots</li>
67-
<li>Local recording to your CloudNode (unlimited, unmetered)</li>
68-
<li>On-device motion detection + alerts</li>
69-
<li>Encrypted recordings + API key (AES-256-GCM)</li>
70-
<li>Camera groups</li>
71-
<li>30-day log retention</li>
72-
</ul>
73-
</div>
74-
75-
<div className="pricing-detail-section">
76-
<div className="pricing-detail-section-label">Not included</div>
77-
<ul className="pricing-detail-missing">
78-
<li>Admin dashboard + stream analytics</li>
79-
<li>MCP integration (AI access)</li>
80-
<li>Outbound webhooks</li>
81-
<li>Priority support</li>
82-
</ul>
83-
</div>
84-
</div>
85-
86-
{/* Pro — the conversion target. Highlighted visually so
87-
a first-time visitor's eye naturally lands here. The
88-
"adds over Free" framing makes the upgrade story obvious. */}
89-
<div className="pricing-detail-card pricing-detail-pro pricing-detail-featured">
90-
<div className="pricing-detail-featured-badge">Most popular</div>
91-
<div className="pricing-detail-card-head">
92-
<h3>Pro</h3>
93-
<div className="pricing-detail-price">
94-
$12<span>/mo</span>
95-
<div className="pricing-detail-annual">or $10/mo billed annually ($120/yr)</div>
96-
</div>
97-
<p className="pricing-detail-for">For small businesses, prosumer setups, and anyone who wants AI access.</p>
98-
</div>
99-
100-
<div className="pricing-detail-section">
101-
<div className="pricing-detail-section-label">Usage</div>
102-
<ul>
103-
<li><strong>300 viewer-hours / month</strong> — 10× Free</li>
104-
<li>Up to <strong>25 cameras</strong> across <strong>10 CloudNodes</strong></li>
105-
<li><strong>10 team seats</strong></li>
106-
<li>30 concurrent live-dashboard connections</li>
107-
</ul>
108-
</div>
109-
110-
<div className="pricing-detail-section">
111-
<div className="pricing-detail-section-label">Everything in Free, plus</div>
112-
<ul>
113-
<li><strong>Admin dashboard</strong> — stream access logs, usage analytics</li>
114-
<li><strong>MCP integration</strong> — connect Claude, Cursor, or custom agents</li>
115-
<li><strong>MCP rate limit:</strong> 30 calls/min · 5,000 calls/day per key</li>
116-
<li><strong>Danger-zone tools</strong> — log wipe, full-reset, key rotation</li>
117-
<li>90-day log retention</li>
118-
<li>Email support</li>
119-
</ul>
120-
</div>
121-
</div>
122-
123-
{/* Pro Plus — positioned against integration-heavy use cases
124-
(multi-site, MSPs, compliance-driven ops). Outbound webhooks
125-
are the headline feature; scale is secondary. */}
126-
<div className="pricing-detail-card pricing-detail-pro-plus">
127-
<div className="pricing-detail-card-head">
128-
<h3>Pro Plus</h3>
129-
<div className="pricing-detail-price">
130-
$29<span>/mo</span>
131-
<div className="pricing-detail-annual">or $25/mo billed annually ($300/yr)</div>
132-
</div>
133-
<p className="pricing-detail-for">For multi-site operators, MSPs, and anyone pushing events into their own stack.</p>
134-
</div>
135-
136-
<div className="pricing-detail-section">
137-
<div className="pricing-detail-section-label">Usage</div>
138-
<ul>
139-
<li><strong>1,500 viewer-hours / month</strong> — 5× Pro</li>
140-
<li>Up to <strong>200 cameras</strong> across <strong>unlimited CloudNodes</strong></li>
141-
<li><strong>20 team seats</strong></li>
142-
<li>100 concurrent live-dashboard connections</li>
143-
</ul>
144-
</div>
145-
146-
<div className="pricing-detail-section">
147-
<div className="pricing-detail-section-label">Everything in Pro, plus</div>
148-
<ul>
149-
<li><strong>Outbound webhooks</strong> — push motion / camera / node events to your own HTTPS endpoint (PagerDuty, Zapier, ticketing, home automation)</li>
150-
<li>HMAC-SHA256 signed deliveries with automatic retry + auto-disable</li>
151-
<li><strong>MCP rate limit:</strong> 120 calls/min · 30,000 calls/day per key (4× Pro)</li>
152-
<li><strong>365-day log retention</strong> — full year of audit history</li>
153-
<li><strong>Priority support</strong> — 24-hour first-response SLA</li>
154-
</ul>
155-
</div>
156-
</div>
157-
</div>
158-
159-
<p className="pricing-detail-footnote">
160-
Need higher caps? If you legitimately need more than 200 cameras
161-
or 1,500 viewer-hours per month, email{" "}
162-
<a href="https://github.com/SourceBox-LLC" target="_blank" rel="noopener noreferrer">
163-
SourceBox LLC
164-
</a>{" "}
165-
— we'd rather raise your bucket than lose a real customer to an
166-
arbitrary ceiling.
167-
</p>
168-
</div>
26+
<p className="pricing-detail-footnote">
27+
Need higher caps? If you legitimately need more than 200 cameras or
28+
1,500 viewer-hours per month, email{" "}
29+
<a href="https://github.com/SourceBox-LLC" target="_blank" rel="noopener noreferrer">
30+
SourceBox LLC
31+
</a>{" "}
32+
— we'd rather raise your bucket than lose a real customer to an
33+
arbitrary ceiling.
34+
</p>
16935

17036
<div className="pricing-features">
17137
<h2 className="pricing-features-title">How usage-based pricing works</h2>

0 commit comments

Comments
 (0)