diff --git a/app/plugins/layout.tsx b/app/plugins/layout.tsx new file mode 100644 index 0000000..24673f2 --- /dev/null +++ b/app/plugins/layout.tsx @@ -0,0 +1,17 @@ +import type { Metadata } from "next"; +import { buildPageMetadata } from "@/lib/seo"; + +export const metadata: Metadata = buildPageMetadata({ + title: "Agent Plugins for Music | Recoup", + description: + "Install Recoup plugins into Claude Code, Cowork, Codex, or Cursor. Artist research, content creation, catalog deals, and more — powered by the Recoup API.", + path: "/plugins", +}); + +export default function PluginsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/plugins/page.tsx b/app/plugins/page.tsx new file mode 100644 index 0000000..b2f75e9 --- /dev/null +++ b/app/plugins/page.tsx @@ -0,0 +1,234 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; +import Link from "next/link"; +import { ArrowRight, Copy, Check, Terminal } from "lucide-react"; +import { pluginsCopy } from "@/lib/copy/plugins"; +import type { Plugin } from "@/lib/copy/plugins"; + +/* ── reveal-on-scroll ── */ +function useReveal() { + const ref = useRef(null); + const [v, setV] = useState(false); + useEffect(() => { + const el = ref.current; + if (!el) return; + const io = new IntersectionObserver( + ([e]) => { + if (e.isIntersecting) { + setV(true); + io.disconnect(); + } + }, + { threshold: 0.08 }, + ); + io.observe(el); + return () => io.disconnect(); + }, []); + return { + ref, + cls: `transition-all duration-[900ms] ease-[cubic-bezier(.16,1,.3,1)] ${ + v ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10" + }`, + }; +} + +/* ── copy button ── */ +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false); + return ( + + ); +} + +/* ── plugin card ── */ +function PluginCard({ plugin, index }: { plugin: Plugin; index: number }) { + const { ref, cls } = useReveal(); + return ( +
+
+ {/* Header */} +
+
+

{plugin.name}

+ + {plugin.skillCount} skill{plugin.skillCount !== 1 ? "s" : ""} + {" · "} + {plugin.audience} + +
+ + GitHub → + +
+ + {/* Description */} +

+ {plugin.description} +

+ + {/* Capabilities */} +
    + {plugin.capabilities.map((cap) => ( +
  • + + {cap} +
  • + ))} +
+ + {/* Install command */} +
+ + {plugin.installCommand} + +
+
+
+ ); +} + +/* ── step card ── */ +function StepCard({ + step, + index, +}: { + step: (typeof pluginsCopy.steps)[number]; + index: number; +}) { + const { ref, cls } = useReveal(); + return ( +
+
+
+ {step.number} +
+

{step.title}

+

+ {step.description} +

+
+
+ ); +} + +/* ── page ── */ +export default function PluginsPage() { + const c = pluginsCopy; + + return ( +
+ {/* Hero */} +
+
+ Plugin Marketplace +
+

+ {c.title} +

+

+ {c.subtitle} +

+ + {/* Compatible-with badges */} +
+ {c.compatibleWith.map((tool) => ( + + {tool} + + ))} +
+
+ + {/* Plugin grid */} +
+ {c.plugins.map((plugin, i) => ( + + ))} +
+ + {/* How it works */} +
+

+ How it works +

+
+ {c.steps.map((step, i) => ( + + ))} +
+
+ + {/* CTA */} +
+
+

+ Ready to plug in? +

+

+ Get your API key, install a plugin, and let your AI agent run your + music business. +

+
+ + Get API Key + + + View Pricing + +
+
+
+
+ ); +} diff --git a/app/results/[slug]/layout.tsx b/app/results/[slug]/layout.tsx new file mode 100644 index 0000000..df3edf4 --- /dev/null +++ b/app/results/[slug]/layout.tsx @@ -0,0 +1,32 @@ +import { Metadata } from "next"; +import { caseStudies } from "@/lib/copy/case-studies"; + +export async function generateStaticParams() { + return caseStudies.map((cs) => ({ slug: cs.slug })); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise { + const { slug } = await params; + const study = caseStudies.find((cs) => cs.slug === slug); + if (!study) return { title: "Case Study | Recoup" }; + return { + title: study.ogTitle, + description: study.ogDescription, + openGraph: { + title: study.ogTitle, + description: study.ogDescription, + }, + }; +} + +export default function CaseStudyLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/app/results/[slug]/page.tsx b/app/results/[slug]/page.tsx new file mode 100644 index 0000000..79e4a2e --- /dev/null +++ b/app/results/[slug]/page.tsx @@ -0,0 +1,311 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; +import { useParams } from "next/navigation"; +import Link from "next/link"; +import { ArrowLeft, ArrowRight, Check } from "lucide-react"; +import { getCaseStudy, caseStudies } from "@/lib/copy/case-studies"; + +/* ── reveal-on-scroll ── */ +function useReveal() { + const ref = useRef(null); + const [v, setV] = useState(false); + useEffect(() => { + const el = ref.current; + if (!el) return; + const io = new IntersectionObserver( + ([e]) => { + if (e.isIntersecting) { + setV(true); + io.disconnect(); + } + }, + { threshold: 0.08 } + ); + io.observe(el); + return () => io.disconnect(); + }, []); + return { + ref, + cls: `transition-all duration-[900ms] ease-[cubic-bezier(.16,1,.3,1)] ${ + v ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10" + }`, + }; +} + +/* ── stat card ── */ +function HeroStat({ value, label }: { value: string; label: string }) { + return ( +
+
+ {value} +
+
{label}
+
+ ); +} + +/* ── solution step ── */ +function SolutionStep({ + step, + index, +}: { + step: { title: string; description: string }; + index: number; +}) { + const { ref, cls } = useReveal(); + return ( +
+
+
+ {index + 1} +
+
+

+ {step.title} +

+

+ {step.description} +

+
+
+
+ ); +} + +/* ── page ── */ +export default function CaseStudyPage() { + const params = useParams(); + const slug = params?.slug as string; + const study = getCaseStudy(slug); + + const heroReveal = useReveal(); + const challengeReveal = useReveal(); + const resultsReveal = useReveal(); + const quoteReveal = useReveal(); + const unlockReveal = useReveal(); + const ctaReveal = useReveal(); + + if (!study) { + return ( +
+
+

Case study not found

+ + ← Back to Results + +
+
+ ); + } + + /* find next case study for navigation */ + const currentIdx = caseStudies.findIndex((cs) => cs.slug === slug); + const nextStudy = caseStudies[(currentIdx + 1) % caseStudies.length]; + + return ( +
+ {/* Back link */} +
+
+ + + All Results + +
+
+ + {/* Hero */} +
+
+ + {study.tag} + +

+ {study.hero.headline} +

+

+ {study.hero.subheadline} +

+
+ {study.hero.stats.map((s) => ( + + ))} +
+
+
+ + {/* Challenge */} +
+
+

+ {study.challenge.headline} +

+
+ {study.challenge.paragraphs.map((p, i) => ( +

+ {p} +

+ ))} +
+
+
+ + {/* Solution */} +
+
+

+ {study.solution.headline} +

+
+ {study.solution.steps.map((step, i) => ( + + ))} +
+
+
+ + {/* Results table */} +
+
+

+ {study.results.headline} +

+
+ {/* header */} +
+
Metric
+
Before Recoup
+
After Recoup
+
+ {/* rows */} + {study.results.rows.map((row, i) => ( +
+
+ {row.metric} +
+
{row.before}
+
{row.after}
+
+ ))} +
+
+
+ + {/* Quote */} + {study.quote && ( +
+
+
+
+ “{study.quote.text}” +
+

+ — {study.quote.attribution} +

+
+
+
+ )} + + {/* What This Unlocked */} +
+
+

+ {study.unlock.headline} +

+
+ {study.unlock.items.map((item) => ( +
+ + {item} +
+ ))} +
+
+
+ + {/* Next case study + CTA */} +
+
+ {/* Next study link */} + {nextStudy && nextStudy.slug !== slug && ( +
+

+ Next Case Study +

+ + {nextStudy.title} + + +
+ )} + + {/* CTA */} +
+

+ Ready to see what AI agents can do for your roster? +

+

+ Start with a free AI readiness audit, or book a strategy session + with Sidney. +

+
+ + Take the Free Audit + + + + Book Advisory Session + +
+
+
+
+
+ ); +} diff --git a/app/results/page.tsx b/app/results/page.tsx index ed815c1..b8003d5 100644 --- a/app/results/page.tsx +++ b/app/results/page.tsx @@ -3,7 +3,8 @@ import { useState, useEffect, useRef } from "react"; import Link from "next/link"; import { ArrowRight, Check } from "lucide-react"; -import { resultsCopy } from "@/lib/copy/results"; +import { resultsCopy, caseStudySummaries } from "@/lib/copy/results"; +import { ArrowRight as ArrowRightIcon } from "lucide-react"; /* ── reveal-on-scroll helper ── */ function useReveal() { @@ -121,6 +122,40 @@ export default function ResultsPage() { + {/* Detailed Case Studies */} +
+
+

+ Deep Dives +

+

+ Full breakdowns with before/after numbers, implementation details, and lessons learned. +

+
+ {caseStudySummaries.map((cs) => ( + + + {cs.tag} + +
+

+ {cs.title} +

+ +
+

+ {cs.description} +

+ + ))} +
+
+
+ {/* Credentials Banner */}
cs.slug === slug); +} + +/** Summary cards for the /results index page */ +export const caseStudySummaries = caseStudies.map((cs) => ({ + slug: cs.slug, + tag: cs.tag, + title: cs.title, + stats: cs.hero.stats, + description: cs.hero.subheadline, +})); diff --git a/lib/copy/plugins.ts b/lib/copy/plugins.ts new file mode 100644 index 0000000..2eea651 --- /dev/null +++ b/lib/copy/plugins.ts @@ -0,0 +1,125 @@ +/** + * Plugins page copy — single source of truth for the plugin showcase. + */ + +export interface Plugin { + id: string; + name: string; + description: string; + skillCount: number; + capabilities: string[]; + installCommand: string; + repo: string; + /** Which tier of customer this targets */ + audience: string; +} + +export const pluginsCopy = { + title: "Agent Plugins for Music", + subtitle: + "Install a plugin. Your AI agent gets music industry superpowers. Works with Claude Code, Cowork, Codex, and Cursor.", + + plugins: [ + { + id: "research", + name: "Research", + description: + "Artist analytics, audience insights, competitive analysis, playlist intelligence, and trend detection. The foundation for any music business decision.", + skillCount: 7, + capabilities: [ + "Artist research & briefs", + "Audience demographics & cities", + "Competitive landscape", + "Playlist intelligence", + "Trend detection", + "Web intelligence", + "People outreach", + ], + installCommand: + "claude plugin install https://github.com/recoupable/recoup-research-plugin", + repo: "https://github.com/recoupable/recoup-research-plugin", + audience: "Labels, managers, distributors", + }, + { + id: "content", + name: "Content", + description: + "Generate release strategy content, social posts, and short-form video scripts. Your agent becomes your marketing team.", + skillCount: 3, + capabilities: [ + "Content creation workflows", + "Social post generation", + "Short-form video scripts", + ], + installCommand: + "claude plugin install https://github.com/recoupable/recoup-content-plugin", + repo: "https://github.com/recoupable/recoup-content-plugin", + audience: "Labels, managers, artists", + }, + { + id: "catalogs", + name: "Catalog Deals", + description: + "Turn a messy seller data room into a source-cited deal package. Royalty normalization, rights review, valuation analysis, financing underwriting, and IC memos — all agent-driven.", + skillCount: 17, + capabilities: [ + "Data room ingestion & normalization", + "Catalog valuation & analysis", + "Rights & ownership review", + "Royalty audit", + "Seller prep & cleanup", + "Financing underwriting", + "IC memo generation", + "Deal dashboard (HTML)", + ], + installCommand: + "claude plugin install https://github.com/recoupable/recoup-catalogs-plugin", + repo: "https://github.com/recoupable/recoup-catalogs-plugin", + audience: "Catalog acquirers, investors, lenders", + }, + { + id: "platform", + name: "Platform", + description: + "Onboarding and diagnostics. Your agent's starting point — validates credentials, discovers capabilities, and runs health checks.", + skillCount: 2, + capabilities: [ + "Guided onboarding flow", + "API key validation", + "Plugin discovery & recommendations", + "Health check & diagnostics", + ], + installCommand: + "claude plugin install https://github.com/recoupable/recoup-platform-plugin", + repo: "https://github.com/recoupable/recoup-platform-plugin", + audience: "Everyone — start here", + }, + ] satisfies Plugin[], + + compatibleWith: [ + "Claude Code", + "Claude Cowork", + "Codex", + "Cursor", + ], + + steps: [ + { + number: "1", + title: "Install", + description: "One command. Plugin loads into your AI tool of choice.", + }, + { + number: "2", + title: "Connect", + description: + "Set your Recoup API key. Get one free at developers.recoupable.com.", + }, + { + number: "3", + title: "Go", + description: + "Ask your agent anything about music. It now has the data and workflows to deliver.", + }, + ], +} as const; diff --git a/lib/copy/results.ts b/lib/copy/results.ts index fbd1570..e2fd883 100644 --- a/lib/copy/results.ts +++ b/lib/copy/results.ts @@ -2,6 +2,8 @@ * Results / case studies page copy — social proof for advisory and SaaS conversion. */ +export { caseStudySummaries } from "./case-studies"; + export const resultsCopy = { title: "Results | Recoup", description: diff --git a/lib/nav.ts b/lib/nav.ts index 956cf3b..f26ab98 100644 --- a/lib/nav.ts +++ b/lib/nav.ts @@ -5,6 +5,7 @@ import { siteConfig } from "@/lib/config"; export const nav: readonly { label: string; href: string; external?: boolean }[] = [ + { label: "Plugins", href: "/plugins" }, { label: "Results", href: "/results" }, { label: "Pricing", href: "/pricing" }, { label: "Docs", href: siteConfig.docsUrl, external: true }, @@ -17,6 +18,7 @@ export const footerNav = { items: [ { label: "Platform", href: "/platform" }, { label: "Pricing", href: "/pricing" }, + { label: "Plugins", href: "/plugins" }, { label: "Docs", href: siteConfig.docsUrl, external: true }, ], },