Skip to content

Commit fdd5ad5

Browse files
Paradigmclaude
andcommitted
Make Quoteline the lead solution for custom-quote businesses
New Quoteline page with use-case tabs and illustrative visuals, featured index card, redirect from the old job shops page, prices removed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent f3feddb commit fdd5ad5

11 files changed

Lines changed: 503 additions & 72 deletions

File tree

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Next.js App Router (TypeScript), static export (`output: "export"`, `trailingSla
2626
- All buttons read "Talk to our team" and go to `/#contact`. No email address is shown.
2727

2828
## Solutions section
29-
Solutions have names: Quoteline (job-shops), Paperflow (logistics), Packet Ready (solar-installers), LabFlow (biotech-data). Names live in `lib/solutions.ts` (`name`, `industry`) and `lib/solutionsNav.ts`; slugs and SEO titles stay industry-based. The names are not trademark-checked.
29+
Solutions have names: Quoteline (`quoteline`, the lead solution for job shops, sign and print shops, caterers and other custom-order businesses), Paperflow (logistics), Packet Ready (solar-installers), LabFlow (biotech-data). `/solutions/job-shops/` is only a redirect page to Quoteline. No price text exists on these pages. A booking button for Quoteline appears only when `BOOKING_URL` in `lib/site.ts` is set. Names live in `lib/solutions.ts` (`name`, `industry`) and `lib/solutionsNav.ts`; slugs and SEO titles stay industry-based. The names are not trademark-checked.
3030
Pre-product framing: pages describe what we build, never what exists. Use "we build", "pilot", "design partner"; never "live", "deployed", "customers", "trusted by". Each page has an example flow and a mock screen labelled "Illustrative concept, sample data". No results, metrics or delivery-proof block appear on these pages (removed at the owner's request). Pricing text stays hidden until `PRICE_BAND_APPROVED` is set to true in `lib/solutions.ts`. `solar-installers` and `biotech-data` are `noindex` and are left out of `public/sitemap.xml` until approved. Solution-page forms add a hidden `topic` (the slug) and an optional "What document or process takes the most time?" field.
3131

3232
## Design

‎app/solutions/[slug]/page.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Metadata } from "next";
22
import { notFound } from "next/navigation";
33
import { SolutionPage } from "@/components/SolutionPage";
4+
import { QuotelinePage } from "@/components/QuotelinePage";
45
import { getSolution, solutions } from "@/lib/solutions";
56

67
export const dynamicParams = false;
@@ -36,5 +37,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
3637
const { slug } = await params;
3738
const s = getSolution(slug);
3839
if (!s) notFound();
40+
if (s.slug === "quoteline") return <QuotelinePage s={s} />;
3941
return <SolutionPage s={s} />;
4042
}

‎app/solutions/job-shops/page.tsx‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Metadata } from "next";
2+
import { Header } from "@/components/Header";
3+
import { Footer } from "@/components/Footer";
4+
5+
// The job shops page moved to Quoteline. GitHub Pages cannot do server redirects, so this small
6+
// page forwards visitors (meta refresh) and points search engines at the new address.
7+
export const metadata: Metadata = {
8+
title: "Quoteline | Paradigm",
9+
alternates: { canonical: "/solutions/quoteline/" },
10+
robots: { index: false, follow: true },
11+
};
12+
13+
export default function JobShopsMoved() {
14+
return (
15+
<>
16+
<meta httpEquiv="refresh" content="0; url=/solutions/quoteline/" />
17+
<Header />
18+
<main id="main" className="mx-auto max-w-2xl px-4 py-32 text-center sm:px-6">
19+
<h1 className="h1">This page has moved</h1>
20+
<p className="mt-4 text-ink">Job shops are now part of Quoteline.</p>
21+
<a href="/solutions/quoteline/" className="btn btn-primary mt-8">
22+
Go to Quoteline
23+
</a>
24+
</main>
25+
<Footer />
26+
</>
27+
);
28+
}

‎app/solutions/page.tsx‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,34 @@ export default function SolutionsIndex() {
4444
</section>
4545

4646
<PageSection>
47-
<ul className="grid gap-5 md:grid-cols-2">
48-
{solutions.map((s) => (
47+
{solutions.slice(0, 1).map((s) => (
48+
<a
49+
key={s.slug}
50+
href={`/solutions/${s.slug}/`}
51+
className="group relative block overflow-hidden rounded-[20px] bg-gradient-to-br from-[#004e23] to-[#00311a] p-8 text-white transition-[transform,box-shadow] duration-300 hover:-translate-y-1 hover:shadow-[0_0_0_1px_#5db368,0_24px_60px_-24px_rgba(48,157,75,0.7)] sm:p-10"
52+
>
53+
<span aria-hidden="true" className="case-orb case-orb-a" />
54+
<span aria-hidden="true" className="case-orb case-orb-b" />
55+
<span aria-hidden="true" className="case-orb case-orb-c" />
56+
<span className="inline-block rounded-full bg-white/10 px-3 py-1 text-sm font-medium text-[#abd49e]">
57+
Lead solution
58+
</span>
59+
<p className="mt-5 max-w-xl text-sm text-[#cfe6c8]">
60+
For {s.industry.toLowerCase()}
61+
</p>
62+
<h2 className="mt-1 font-display text-4xl font-medium leading-tight">{s.name}</h2>
63+
<p className="mt-3 max-w-2xl text-lg text-[#cfe6c8]">{s.cardText}</p>
64+
<span className="mt-6 inline-block text-sm font-medium text-[#abd49e] group-hover:underline">
65+
See what we build
66+
</span>
67+
</a>
68+
))}
69+
<ul className="mt-5 grid gap-5 md:grid-cols-3">
70+
{solutions.slice(1).map((s) => (
4971
<li key={s.slug}>
5072
<a
5173
href={`/solutions/${s.slug}/`}
52-
className="group block h-full rounded-card border border-line bg-paper p-6 transition-[background-color,border-color,transform] duration-300 hover:-translate-y-0.5 hover:border-growth hover:bg-sprout sm:p-8"
74+
className="group block h-full rounded-card border border-line bg-paper p-6 transition-[background-color,border-color,transform] duration-300 hover:-translate-y-0.5 hover:border-growth hover:bg-sprout"
5375
>
5476
<span aria-hidden="true" className="mini-dot mb-4 block h-3 w-3 rounded-full bg-leaf" />
5577
<p className="text-sm text-ink">For {s.industry.toLowerCase()}</p>

‎components/ExampleFlow.tsx‎

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,33 @@ const mocks: Record<MockKind, { el: React.ReactNode; alt: string }> = {
178178
},
179179
};
180180

181+
export function FlowSteps({ steps }: { steps: string[] }) {
182+
return (
183+
<figure>
184+
<ol
185+
aria-label={`Example flow (${ILLUSTRATION_CAPTION})`}
186+
className="flex flex-col gap-3 md:flex-row md:items-stretch md:gap-0"
187+
>
188+
{steps.map((s, i) => (
189+
<li
190+
key={s}
191+
className="relative flex flex-1 items-center gap-3 rounded-xl border border-line bg-paper px-4 py-3 text-sm md:mx-0 md:rounded-none md:first:rounded-l-xl md:last:rounded-r-xl md:[&:not(:first-child)]:border-l-0"
192+
>
193+
<span
194+
aria-hidden="true"
195+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-forest text-xs font-medium text-white"
196+
>
197+
{i + 1}
198+
</span>
199+
<span>{s}</span>
200+
</li>
201+
))}
202+
</ol>
203+
<figcaption className="mt-2 text-xs text-ink">{ILLUSTRATION_CAPTION}</figcaption>
204+
</figure>
205+
);
206+
}
207+
181208
export function ExampleFlow({
182209
intro,
183210
steps,
@@ -192,28 +219,9 @@ export function ExampleFlow({
192219
<div className="mt-10">
193220
<p className="text-ink">{intro}</p>
194221

195-
<figure className="mt-4">
196-
<ol
197-
aria-label={`Example flow (${ILLUSTRATION_CAPTION})`}
198-
className="flex flex-col gap-3 md:flex-row md:items-stretch md:gap-0"
199-
>
200-
{steps.map((s, i) => (
201-
<li
202-
key={s}
203-
className="relative flex flex-1 items-center gap-3 rounded-xl border border-line bg-paper px-4 py-3 text-sm md:mx-0 md:rounded-none md:first:rounded-l-xl md:last:rounded-r-xl md:[&:not(:first-child)]:border-l-0"
204-
>
205-
<span
206-
aria-hidden="true"
207-
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-forest text-xs font-medium text-white"
208-
>
209-
{i + 1}
210-
</span>
211-
<span>{s}</span>
212-
</li>
213-
))}
214-
</ol>
215-
<figcaption className="mt-2 text-xs text-ink">{ILLUSTRATION_CAPTION}</figcaption>
216-
</figure>
222+
<div className="mt-4">
223+
<FlowSteps steps={steps} />
224+
</div>
217225

218226
<figure className="mt-8">
219227
<div role="img" aria-label={m.alt} className="rounded-2xl bg-sand p-4 sm:p-5">

‎components/QuotelinePage.tsx‎

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { Header } from "@/components/Header";
2+
import { Footer } from "@/components/Footer";
3+
import { Breadcrumb } from "@/components/Breadcrumb";
4+
import { PageSection } from "@/components/PageSection";
5+
import { FlowSteps } from "@/components/ExampleFlow";
6+
import { UseCaseTabs } from "@/components/UseCaseTabs";
7+
import { ContactForm } from "@/components/ContactForm";
8+
import { faqsFor, type Solution } from "@/lib/solutions";
9+
import { BOOKING_LABEL, BOOKING_URL, SITE_URL } from "@/lib/site";
10+
11+
const card = "rounded-card border border-line bg-paper p-6 sm:p-8";
12+
const TAGLINE = "Every quote request, from every inbox, drafted into a quote you approve.";
13+
14+
export function QuotelinePage({ s }: { s: Solution }) {
15+
const faqs = faqsFor(s);
16+
const jsonLd = [
17+
{
18+
"@context": "https://schema.org",
19+
"@type": "FAQPage",
20+
mainEntity: faqs.map((f) => ({
21+
"@type": "Question",
22+
name: f.q,
23+
acceptedAnswer: { "@type": "Answer", text: f.a },
24+
})),
25+
},
26+
{
27+
"@context": "https://schema.org",
28+
"@type": "BreadcrumbList",
29+
itemListElement: [
30+
{ "@type": "ListItem", position: 1, name: "Home", item: `${SITE_URL}/` },
31+
{ "@type": "ListItem", position: 2, name: "Solutions", item: `${SITE_URL}/solutions/` },
32+
{ "@type": "ListItem", position: 3, name: s.name, item: `${SITE_URL}/solutions/${s.slug}/` },
33+
],
34+
},
35+
];
36+
37+
return (
38+
<>
39+
<Header />
40+
<main id="main">
41+
{/* 1. Hero */}
42+
<section className="bg-canvas">
43+
<div className="mx-auto max-w-6xl px-4 pb-16 pt-8 sm:px-6 md:pb-20">
44+
<Breadcrumb
45+
items={[
46+
{ label: "Home", href: "/" },
47+
{ label: "Solutions", href: "/solutions/" },
48+
{ label: s.name },
49+
]}
50+
/>
51+
<p className="mt-10 flex flex-wrap items-center gap-x-3 gap-y-1">
52+
<span className="rounded-full bg-sprout px-3 py-1 text-sm font-medium text-forest">
53+
{s.name}
54+
</span>
55+
<span className="text-sm text-ink">for custom-order businesses</span>
56+
</p>
57+
<h1 className="h1 mt-5 max-w-3xl">{s.headline}</h1>
58+
<p className="mt-5 max-w-2xl text-lg text-ink">
59+
Quoteline gathers requests from your email, web form and messages, and drafts the
60+
quote for you to approve.
61+
</p>
62+
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
63+
<a href="#contact" className="btn btn-primary">
64+
Talk to our team
65+
</a>
66+
<a href="#contact" className="btn btn-ghost">
67+
Become a design partner
68+
</a>
69+
</div>
70+
</div>
71+
</section>
72+
73+
{/* 2. In your words */}
74+
<PageSection>
75+
<h2 className="h2">In your words</h2>
76+
<ul className="mt-8 grid gap-4 md:grid-cols-3">
77+
{s.problems.map((p) => (
78+
<li key={p} className={card}>
79+
<p className="font-display text-xl leading-snug">&ldquo;{p}&rdquo;</p>
80+
</li>
81+
))}
82+
</ul>
83+
</PageSection>
84+
85+
{/* 3 + 4. What we build + flow */}
86+
<PageSection tone="canvas">
87+
<h2 className="h2">What we build</h2>
88+
<p className="mt-4 max-w-3xl font-display text-2xl leading-snug">{TAGLINE}</p>
89+
<p className="mt-4 max-w-3xl text-lg text-ink">{s.build}</p>
90+
<div className="mt-10">
91+
<p className="text-ink">{s.flowIntro}</p>
92+
<div className="mt-4">
93+
<FlowSteps steps={s.flow} />
94+
</div>
95+
</div>
96+
</PageSection>
97+
98+
{/* 5 + 6. Who it is for + visuals */}
99+
<PageSection>
100+
<h2 className="h2">Who it is for</h2>
101+
<p className="mt-4 max-w-3xl text-lg text-ink">
102+
Any small business that answers requests for custom quotes. Here is how a request could
103+
look in three kinds of business.
104+
</p>
105+
<div className="mt-8">
106+
<UseCaseTabs />
107+
</div>
108+
</PageSection>
109+
110+
{/* 7. How it works + timeline */}
111+
<PageSection tone="canvas">
112+
<h2 className="h2">How it works</h2>
113+
<ol className="mt-8 grid gap-4 md:grid-cols-3">
114+
{s.steps.map((step, i) => (
115+
<li key={step} className={card}>
116+
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-forest text-sm font-medium text-white">
117+
{i + 1}
118+
</span>
119+
<p className="mt-4 text-ink">{step}</p>
120+
</li>
121+
))}
122+
</ol>
123+
<div className="mt-6 rounded-card bg-sprout px-6 py-4 text-forest">
124+
<span className="font-medium">Typical timeline: </span>
125+
{s.timeline}
126+
</div>
127+
</PageSection>
128+
129+
{/* 8. What we need */}
130+
<PageSection>
131+
<h2 className="h2">What we need from you</h2>
132+
<ul className="mt-8 grid gap-3 sm:grid-cols-2">
133+
{s.need.map((n) => (
134+
<li key={n} className="flex items-start gap-3 rounded-card border border-line bg-paper px-5 py-4">
135+
<span aria-hidden="true" className="mt-2 h-2.5 w-2.5 shrink-0 rounded-full bg-leaf" />
136+
<span>{n}</span>
137+
</li>
138+
))}
139+
</ul>
140+
</PageSection>
141+
142+
{/* 9. FAQ */}
143+
<PageSection tone="canvas">
144+
<h2 className="h2">Questions</h2>
145+
<div className="mt-8 max-w-3xl divide-y divide-line border-y border-line">
146+
{faqs.map((f) => (
147+
<details key={f.q} className="group py-5">
148+
<summary className="flex cursor-pointer items-center justify-between gap-4 text-lg font-medium">
149+
{f.q}
150+
<span
151+
aria-hidden="true"
152+
className="text-2xl leading-none text-forest transition-transform group-open:rotate-45"
153+
>
154+
+
155+
</span>
156+
</summary>
157+
<p className="mt-3 text-ink">{f.a}</p>
158+
</details>
159+
))}
160+
</div>
161+
</PageSection>
162+
163+
{/* 10. CTA block + form */}
164+
<PageSection id="contact">
165+
<div className="mx-auto max-w-2xl rounded-card bg-sprout p-6 text-left sm:p-8">
166+
<h2 className="h2 text-forest">{s.ctaLine}</h2>
167+
<p className="mt-3 text-[#2b3a2e]">
168+
We are taking a small number of design partners. You get a pilot on your own requests
169+
at a reduced fixed price, and we shape the product around how you quote.
170+
</p>
171+
{BOOKING_URL && (
172+
<a
173+
href={BOOKING_URL}
174+
target="_blank"
175+
rel="noopener noreferrer"
176+
className="btn btn-primary mt-6"
177+
>
178+
{BOOKING_LABEL}
179+
</a>
180+
)}
181+
</div>
182+
<div className="mx-auto mt-6 max-w-2xl rounded-card border border-line bg-paper p-6 sm:p-8">
183+
<ContactForm topic={s.slug} processField />
184+
</div>
185+
</PageSection>
186+
</main>
187+
<Footer />
188+
{jsonLd.map((d, i) => (
189+
<script key={i} type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(d) }} />
190+
))}
191+
</>
192+
);
193+
}

0 commit comments

Comments
 (0)