Skip to content

feat: interactive ROI calculator at /tools/roi + resources hub#50

Open
sidneyswift wants to merge 2 commits into
mainfrom
feat/roi-calculator
Open

feat: interactive ROI calculator at /tools/roi + resources hub#50
sidneyswift wants to merge 2 commits into
mainfrom
feat/roi-calculator

Conversation

@sidneyswift

@sidneyswift sidneyswift commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Interactive ROI calculator with 4 sliders (roster size, hours/artist/week, team cost, posts/artist/week). Animated result cards showing time saved, cost saved, content boost, ROI multiple. Also overhauls /resources from 'Coming soon' to real links hub.

New files: app/tools/roi/page.tsx, components/roi/ROICalculator.tsx, lib/copy/roi.ts, custom slider CSS.


Summary by cubic

Adds an interactive ROI calculator at /tools/roi and replaces the /resources placeholder with a simple links hub. Helps teams quickly estimate time and cost savings, content boost, and ROI.

  • New Features
    • New /tools/roi calculator with 4 sliders: roster size, hours/artist/week, team hourly cost, posts/artist/week.
    • Animated results for annual hours saved, annual cost saved, content boost, ROI multiple, plus a weekly hours comparison bar.
    • Copy centralized in lib/copy/roi.ts; custom slider styling added in app/globals.css.
    • /resources page now links to the ROI calculator, blog, playbook, and demos.

Written for commit 0a24caa. Summary will update on new commits.

Review in cubic

Recoupable added 2 commits July 10, 2026 01:06
- New /tools/roi page with 4-slider calculator (roster, hours, cost, content)
- Animated result cards: time saved, cost saved, content boost, ROI multiple
- Comparison bar showing weekly hours with vs without Recoup
- Copy file lib/copy/roi.ts (single source of truth)
- Custom slider CSS in globals.css
- Resources page now links to ROI calc, blog, playbook, demos (was 'Coming soon')
- TypeScript clean (no new errors)
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marketing Ready Ready Preview Jul 15, 2026 8:56pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sidneyswift, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26fcc088-c59d-4a14-b50b-d36f2b1fff37

📥 Commits

Reviewing files that changed from the base of the PR and between d006b91 and 0a24caa.

📒 Files selected for processing (5)
  • app/globals.css
  • app/resources/page.tsx
  • app/tools/roi/page.tsx
  • components/roi/ROICalculator.tsx
  • lib/copy/roi.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/roi-calculator

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/roi/ROICalculator.tsx">

<violation number="1" location="components/roi/ROICalculator.tsx:46">
P3: Users requesting reduced motion still get number animations on every slider update. Skip the RAF animation when `prefers-reduced-motion: reduce` matches.</violation>

<violation number="2" location="components/roi/ROICalculator.tsx:87">
P2: The slider inputs lack a programmatic label association. Each `<label>` in `SliderInput` has no `htmlFor` and the corresponding `<input>` has no `id`, so assistive technology cannot associate the visible label with the range control. This means a screen reader user hears an unnamed slider instead of something like "Artists in your roster, slider 10". Add `useId()` from React (available in React 19) to generate a unique ID and wire it via `htmlFor` on the label and `id` on the input.</violation>

<violation number="3" location="components/roi/ROICalculator.tsx:223">
P2: Changing “Posts per artist per week” never changes a displayed result: `contentBoost` remains 150% because both terms scale equally. Show the resulting content volume or remove this input so every slider has an observable calculation effect.</violation>
</file>

<file name="app/resources/page.tsx">

<violation number="1" location="app/resources/page.tsx:7">
P2: Title will render with a duplicated `| Recoup` suffix. The root layout already applies `title: { template: "%s | Recoup" }`, so the page-level string `Resources — Tools, Blog & More | Recoup` gets wrapped again producing `Resources — Tools, Blog & More | Recoup | Recoup` in the browser tab and OG tags.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

const annualTimeSaved = Math.round(weeklyHours * automationRate * 52);
const annualCostSaved = Math.round(annualManualCost * automationRate);

const contentWithout = postsPerWeek * roster * 52;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Changing “Posts per artist per week” never changes a displayed result: contentBoost remains 150% because both terms scale equally. Show the resulting content volume or remove this input so every slider has an observable calculation effect.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/roi/ROICalculator.tsx, line 223:

<comment>Changing “Posts per artist per week” never changes a displayed result: `contentBoost` remains 150% because both terms scale equally. Show the resulting content volume or remove this input so every slider has an observable calculation effect.</comment>

<file context>
@@ -0,0 +1,341 @@
+    const annualTimeSaved = Math.round(weeklyHours * automationRate * 52);
+    const annualCostSaved = Math.round(annualManualCost * automationRate);
+
+    const contentWithout = postsPerWeek * roster * 52;
+    const contentWith = Math.round(contentWithout * 2.5);
+    const contentBoost = contentWithout > 0
</file context>

Comment thread app/resources/page.tsx

export const metadata: Metadata = buildPageMetadata({
title: `Resources — Blog & Demos | ${siteConfig.name}`,
title: `Resources — Tools, Blog & More | ${siteConfig.name}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Title will render with a duplicated | Recoup suffix. The root layout already applies title: { template: "%s | Recoup" }, so the page-level string Resources — Tools, Blog & More | Recoup gets wrapped again producing Resources — Tools, Blog & More | Recoup | Recoup in the browser tab and OG tags.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/resources/page.tsx, line 7:

<comment>Title will render with a duplicated `| Recoup` suffix. The root layout already applies `title: { template: "%s | Recoup" }`, so the page-level string `Resources — Tools, Blog & More | Recoup` gets wrapped again producing `Resources — Tools, Blog & More | Recoup | Recoup` in the browser tab and OG tags.</comment>

<file context>
@@ -1,23 +1,88 @@
 
 export const metadata: Metadata = buildPageMetadata({
-  title: `Resources — Blog & Demos | ${siteConfig.name}`,
+  title: `Resources — Tools, Blog & More | ${siteConfig.name}`,
   description:
-    "Blog posts and demos. Everything you need to run your music business with AI agents — strategy, content, fans, and operations.",
</file context>

return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-[13px] text-[var(--muted-foreground)] tracking-wide">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The slider inputs lack a programmatic label association. Each <label> in SliderInput has no htmlFor and the corresponding <input> has no id, so assistive technology cannot associate the visible label with the range control. This means a screen reader user hears an unnamed slider instead of something like "Artists in your roster, slider 10". Add useId() from React (available in React 19) to generate a unique ID and wire it via htmlFor on the label and id on the input.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/roi/ROICalculator.tsx, line 87:

<comment>The slider inputs lack a programmatic label association. Each `<label>` in `SliderInput` has no `htmlFor` and the corresponding `<input>` has no `id`, so assistive technology cannot associate the visible label with the range control. This means a screen reader user hears an unnamed slider instead of something like "Artists in your roster, slider 10". Add `useId()` from React (available in React 19) to generate a unique ID and wire it via `htmlFor` on the label and `id` on the input.</comment>

<file context>
@@ -0,0 +1,341 @@
+  return (
+    <div className="space-y-3">
+      <div className="flex items-center justify-between">
+        <label className="text-[13px] text-[var(--muted-foreground)] tracking-wide">
+          {label}
+        </label>
</file context>

if (t < 1) raf = requestAnimationFrame(tick);
}

raf = requestAnimationFrame(tick);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Users requesting reduced motion still get number animations on every slider update. Skip the RAF animation when prefers-reduced-motion: reduce matches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/roi/ROICalculator.tsx, line 46:

<comment>Users requesting reduced motion still get number animations on every slider update. Skip the RAF animation when `prefers-reduced-motion: reduce` matches.</comment>

<file context>
@@ -0,0 +1,341 @@
+      if (t < 1) raf = requestAnimationFrame(tick);
+    }
+
+    raf = requestAnimationFrame(tick);
+    return () => cancelAnimationFrame(raf);
+  }, [value, prefix, suffix, duration]);
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant