deploy: polish public page formatting

This commit is contained in:
DMleadgen 2026-04-17 12:26:23 -06:00
parent 7144aa4943
commit 656b78bf8e
Signed by: matt
GPG key ID: C2720CF8CD701894
8 changed files with 500 additions and 307 deletions

View file

@ -10,10 +10,18 @@ import { FAQSection } from "@/components/faq-section"
import { ContactPage } from "@/components/contact-page" import { ContactPage } from "@/components/contact-page"
import { AboutPage } from "@/components/about-page" import { AboutPage } from "@/components/about-page"
import { WhoWeServePage } from "@/components/who-we-serve-page" import { WhoWeServePage } from "@/components/who-we-serve-page"
import { Breadcrumbs } from "@/components/breadcrumbs"
import {
PublicInset,
PublicPageHeader,
PublicProse,
PublicSurface,
} from "@/components/public-surface"
import { import {
generateLocationPageMetadata, generateLocationPageMetadata,
LocationLandingPage, LocationLandingPage,
} from "@/components/location-landing-page" } from "@/components/location-landing-page"
import Link from "next/link"
// Required for static export - ensures this route is statically generated // Required for static export - ensures this route is statically generated
export const dynamic = "force-static" export const dynamic = "force-static"
@ -48,6 +56,7 @@ const routeMapping: Record<string, string> = {
// Food & Beverage // Food & Beverage
"food-and-beverage/healthy-options": "healthy-vending", "food-and-beverage/healthy-options": "healthy-vending",
"food-and-beverage/snack-and-drink-delivery": "snack-and-drink-delivery",
"food-and-beverage/traditional-options": "traditional-vending", "food-and-beverage/traditional-options": "traditional-vending",
"food-and-beverage/suppliers": "food-and-beverage/suppliers":
"diverse-vending-options-with-rocky-mountain-vendings-exclusive-wholesale-accounts", "diverse-vending-options-with-rocky-mountain-vendings-exclusive-wholesale-accounts",
@ -348,6 +357,7 @@ export default async function WordPressPage({ params }: PageProps) {
"vending-machines-for-your-car-wash", "vending-machines-for-your-car-wash",
] ]
const isWhoWeServePage = whoWeServeSlugs.includes(pageSlug) const isWhoWeServePage = whoWeServeSlugs.includes(pageSlug)
const routePath = `/${slugArray.join("/")}`
return ( return (
<> <>
@ -377,13 +387,58 @@ export default async function WordPressPage({ params }: PageProps) {
pageSlug !== "contact-us" && pageSlug !== "contact-us" &&
pageSlug !== "about-us" && pageSlug !== "about-us" &&
!isWhoWeServePage && ( !isWhoWeServePage && (
<article className="container mx-auto px-4 py-8 md:py-12 max-w-4xl"> <article className="container mx-auto max-w-5xl px-4 py-10 md:py-14">
<header className="mb-8"> <Breadcrumbs
<h1 className="text-4xl md:text-5xl font-bold mb-6"> className="mb-6"
{page.title || "Page"} items={[
</h1> { label: "Home", href: "/" },
</header> { label: page.title || "Page", href: routePath },
{content} ]}
/>
<PublicPageHeader
eyebrow={pageSlug.startsWith("blog") ? "Article" : "Information"}
title={page.title || "Page"}
description={
page.seoDescription ||
page.excerpt ||
"Explore the details, service guidance, and next steps from Rocky Mountain Vending."
}
className="mx-auto mb-10 max-w-3xl text-center"
align="center"
/>
<PublicSurface className="p-5 md:p-7 lg:p-9">
<PublicProse className="mx-auto max-w-3xl">{content}</PublicProse>
</PublicSurface>
<PublicInset className="mx-auto mt-8 max-w-4xl border-primary/12 bg-[linear-gradient(180deg,rgba(41,160,71,0.06),rgba(255,255,255,0.84))] p-5 md:p-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Need Help Choosing The Right Next Step?
</p>
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-foreground">
Talk with Rocky Mountain Vending
</h2>
<p className="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
Reach out about placement, machine sales, repairs, moving help,
manuals, or parts and we&apos;ll point you in the right direction.
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<Link
href="/contact-us#contact-form"
className="inline-flex min-h-11 items-center justify-center rounded-full bg-primary px-5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90"
>
Talk to Our Team
</Link>
<Link
href="/#request-machine"
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-white px-5 text-sm font-medium text-foreground transition hover:border-primary/35 hover:text-primary"
>
See If You Qualify
</Link>
</div>
</div>
</PublicInset>
</article> </article>
)} )}
</> </>

View file

@ -5,7 +5,14 @@ import { generateSEOMetadata, generateStructuredData } from "@/lib/seo"
import { getPageBySlug } from "@/lib/wordpress-data-loader" import { getPageBySlug } from "@/lib/wordpress-data-loader"
import { cleanWordPressContent } from "@/lib/clean-wordPress-content" import { cleanWordPressContent } from "@/lib/clean-wordPress-content"
import { Breadcrumbs } from "@/components/breadcrumbs" import { Breadcrumbs } from "@/components/breadcrumbs"
import {
PublicInset,
PublicPageHeader,
PublicProse,
PublicSurface,
} from "@/components/public-surface"
import type { Metadata } from "next" import type { Metadata } from "next"
import Link from "next/link"
const WORDPRESS_SLUG = "abandoned-vending-machines" const WORDPRESS_SLUG = "abandoned-vending-machines"
@ -81,17 +88,61 @@ export default async function AbandonedVendingMachinesPage() {
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/> />
<Breadcrumbs <article className="container mx-auto max-w-5xl px-4 py-10 md:py-14">
items={[ <Breadcrumbs
{ label: "Blog", href: "/blog" }, className="mb-6"
{ items={[
label: page.title || "Abandoned Vending Machines", { label: "Blog", href: "/blog" },
href: "/blog/abandoned-vending-machines", {
}, label: page.title || "Abandoned Vending Machines",
]} href: "/blog/abandoned-vending-machines",
/> },
<article className="container mx-auto px-4 py-8 md:py-12 max-w-4xl"> ]}
{content} />
<PublicPageHeader
eyebrow="Article"
title={page.title || "Abandoned Vending Machines"}
description={
page.seoDescription ||
page.excerpt ||
"Guidance, next steps, and practical considerations from Rocky Mountain Vending."
}
align="center"
className="mx-auto mb-10 max-w-3xl"
/>
<PublicSurface className="p-5 md:p-7 lg:p-9">
<PublicProse className="mx-auto max-w-3xl">{content}</PublicProse>
</PublicSurface>
<PublicInset className="mx-auto mt-8 max-w-4xl border-primary/12 bg-[linear-gradient(180deg,rgba(41,160,71,0.06),rgba(255,255,255,0.84))] p-5 md:p-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Need Help With A Machine Situation?
</p>
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-foreground">
Get the right kind of support quickly
</h2>
<p className="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
Reach out if you need help with abandoned machines, service questions,
moving help, or figuring out the right next step for your location.
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<Link
href="/contact-us#contact-form"
className="inline-flex min-h-11 items-center justify-center rounded-full bg-primary px-5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90"
>
Talk to Our Team
</Link>
<Link
href="/services/repairs"
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-white px-5 text-sm font-medium text-foreground transition hover:border-primary/35 hover:text-primary"
>
Explore Repair Help
</Link>
</div>
</div>
</PublicInset>
</article> </article>
</> </>
) )

View file

@ -5,6 +5,7 @@ import { Breadcrumbs, type BreadcrumbItem } from "@/components/breadcrumbs"
import { import {
PublicInset, PublicInset,
PublicPageHeader, PublicPageHeader,
PublicProse,
PublicSurface, PublicSurface,
} from "@/components/public-surface" } from "@/components/public-surface"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@ -64,19 +65,19 @@ export function DropdownPageShell({
</PublicPageHeader> </PublicPageHeader>
{contentIntro ? ( {contentIntro ? (
<section className="mt-10 grid gap-4 lg:grid-cols-2">{contentIntro}</section> <section className="mt-10 grid gap-5 lg:grid-cols-2">{contentIntro}</section>
) : null} ) : null}
<section className={cn(contentIntro ? "mt-6" : "mt-10")}> <section className={cn(contentIntro ? "mt-8" : "mt-10")}>
<PublicSurface <PublicSurface
className={cn( className={cn(
"relative overflow-hidden p-0 md:p-0", "relative overflow-hidden p-0 md:p-0",
contentSurfaceClassName contentSurfaceClassName
)} )}
> >
<div className="absolute inset-x-0 top-0 h-18 bg-[radial-gradient(circle_at_top,rgba(41,160,71,0.11),transparent_72%)]" /> <div className="absolute inset-x-0 top-0 h-24 bg-[radial-gradient(circle_at_top,rgba(41,160,71,0.09),transparent_74%)]" />
<div className="relative p-5 md:p-7 lg:p-9"> <div className="relative p-5 md:p-7 lg:p-10">
<div className="mb-6 flex items-center justify-between gap-4 border-b border-border/55 pb-4"> <div className="mb-8 flex items-center justify-between gap-4 border-b border-border/55 pb-5">
<div> <div>
<p className="text-[0.72rem] font-semibold uppercase tracking-[0.22em] text-primary/80"> <p className="text-[0.72rem] font-semibold uppercase tracking-[0.22em] text-primary/80">
Location Guide Location Guide
@ -87,15 +88,17 @@ export function DropdownPageShell({
</p> </p>
</div> </div>
</div> </div>
<div className={cn("mx-auto max-w-3xl", contentClassName)}>{content}</div> <PublicProse className={cn("mx-auto max-w-3xl", contentClassName)}>
{content}
</PublicProse>
</div> </div>
</PublicSurface> </PublicSurface>
</section> </section>
{sections ? <div className="mt-12 space-y-12">{sections}</div> : null} {sections ? <div className="mt-14 space-y-14">{sections}</div> : null}
{cta ? ( {cta ? (
<section className="mt-12"> <section className="mt-14">
<PublicSurface className="overflow-hidden text-center"> <PublicSurface className="overflow-hidden text-center">
<div className="absolute inset-x-0 top-0 h-20 bg-[radial-gradient(circle_at_top,rgba(41,160,71,0.10),transparent_70%)]" /> <div className="absolute inset-x-0 top-0 h-20 bg-[radial-gradient(circle_at_top,rgba(41,160,71,0.10),transparent_70%)]" />
{cta.eyebrow ? ( {cta.eyebrow ? (

View file

@ -114,11 +114,52 @@ export function LocationLandingPage({
}: { }: {
locationData: LocationData locationData: LocationData
}) { }) {
const isSaltLakeCity = locationData.slug === "salt-lake-city-utah"
const countyName = getCountyName(locationData.slug) const countyName = getCountyName(locationData.slug)
const industries = getIndustryFocus(locationData) const industries = getIndustryFocus(locationData)
const canonicalUrl = buildAbsoluteUrl(buildLocationRoute(locationData.slug)) const canonicalUrl = buildAbsoluteUrl(buildLocationRoute(locationData.slug))
const title = `Vending Machines in ${locationData.city}, ${locationData.stateAbbr}` const title = `Vending Machines in ${locationData.city}, ${locationData.stateAbbr}`
const description = `Rocky Mountain Vending provides free placement for qualifying locations, machine sales, repairs, and vending service for businesses in ${locationData.city}, ${locationData.stateAbbr}.` const description = `Rocky Mountain Vending provides free placement for qualifying locations, machine sales, repairs, and vending service for businesses in ${locationData.city}, ${locationData.stateAbbr}.`
const comparisonRows = [
["Credit card readers", "Yes", "Maybe", "Yes"],
["Locally owned", "Yes", "Yes", "Maybe"],
["Fast service", "Yes", "Maybe", "Maybe"],
["Large selection of products", "Yes", "Maybe", "Yes"],
["Quality of equipment used", "Excellent", "Varies", "Excellent"],
["Locked into Coke or Pepsi equipment", "No", "Maybe", "Probably"],
]
const saltLakeServiceLinks = [
{
title: "Traditional snacks and drinks",
body: "Stock the machine with the classic snacks, sodas, and convenience items most locations still want every day.",
href: "/food-and-beverage/traditional-options",
},
{
title: "Healthy snacks and drinks",
body: "Offer protein bars, better-for-you snacks, and drink choices that fit health-conscious teams and customers.",
href: "/food-and-beverage/healthy-options",
},
{
title: "Snack and drink delivery",
body: "Need product delivery or a broader refreshment setup beyond standard machine placement? We can help there too.",
href: "/food-and-beverage/snack-and-drink-delivery",
},
{
title: "Vending machine sales",
body: "Compare purchase options if you want equipment ownership instead of a free-placement arrangement.",
href: "/vending-machines/machines-for-sale",
},
{
title: "Parts, repairs, and moving",
body: "Get support for repair work, machine moving, replacement parts, and operational issues that need direct service help.",
href: "/services/parts",
},
{
title: "Training and support",
body: "Browse the support pages and machine guides if you need help with specific models, manuals, or machine operation questions.",
href: "/blog",
},
]
const structuredData = { const structuredData = {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -188,15 +229,15 @@ export function LocationLandingPage({
<PublicPageHeader <PublicPageHeader
align="center" align="center"
eyebrow="Local Service Area" eyebrow="Local Service Area"
title={`${locationData.city}, ${locationData.stateAbbr} vending machine service`} title={`Vending machine service for businesses in ${locationData.city}, ${locationData.stateAbbr}`}
description={`Rocky Mountain Vending serves businesses in ${locationData.city}, ${locationData.stateAbbr} with free placement for qualifying locations, machine sales, repairs, parts, and ongoing restocking and service across ${countyName} and nearby communities.`} description={`Rocky Mountain Vending helps businesses in ${locationData.city} with free placement for qualifying locations, machine sales, repairs, parts, and ongoing service across ${countyName} and nearby communities.`}
className="mb-12 md:mb-16" className="mb-12 md:mb-16"
/> />
<div className="mx-auto mb-12 grid max-w-5xl gap-6 lg:grid-cols-[1.1fr_0.9fr]"> <div className="mx-auto mb-14 grid max-w-5xl gap-6 lg:grid-cols-[1.08fr_0.92fr]">
<PublicSurface className="p-6 md:p-8"> <PublicSurface className="p-6 md:p-8">
<h2 className="text-2xl font-semibold tracking-tight text-balance"> <h2 className="text-2xl font-semibold tracking-tight text-balance">
Vending service for businesses across {locationData.city} A local vending partner for businesses across {locationData.city}
</h2> </h2>
<p className="mt-4 text-base leading-relaxed text-muted-foreground"> <p className="mt-4 text-base leading-relaxed text-muted-foreground">
If your business is in {locationData.neighborhoods.join(", ")}, or If your business is in {locationData.neighborhoods.join(", ")}, or
@ -219,11 +260,11 @@ export function LocationLandingPage({
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance"> <h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance">
Common business types we serve in {locationData.city} Common business types we serve in {locationData.city}
</h2> </h2>
<div className="mt-5 flex flex-wrap gap-2"> <div className="mt-5 flex flex-wrap gap-2.5">
{industries.map((industry) => ( {industries.map((industry) => (
<span <span
key={industry} key={industry}
className="rounded-full border border-border/60 bg-background px-3 py-1 text-sm text-muted-foreground" className="rounded-full border border-border/60 bg-background px-3 py-1.5 text-sm text-muted-foreground"
> >
{industry} {industry}
</span> </span>
@ -236,6 +277,15 @@ export function LocationLandingPage({
Utah service area. Utah service area.
</p> </p>
</PublicInset> </PublicInset>
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
<GetFreeMachineCta buttonLabel="Check Placement Fit" />
<Link
href="/contact-us#contact-form"
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-white px-4 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
>
Talk to Our Team
</Link>
</div>
</PublicSurface> </PublicSurface>
</div> </div>
@ -243,7 +293,7 @@ export function LocationLandingPage({
<h2 className="text-3xl font-bold tracking-tight text-balance"> <h2 className="text-3xl font-bold tracking-tight text-balance">
Vending services available in {locationData.city} Vending services available in {locationData.city}
</h2> </h2>
<div className="mt-8 grid gap-6 md:grid-cols-2"> <div className="mt-8 grid gap-5 md:grid-cols-2">
{[ {[
{ {
title: "Free vending placement", title: "Free vending placement",
@ -270,7 +320,7 @@ export function LocationLandingPage({
cta: "View manuals and parts", cta: "View manuals and parts",
}, },
].map((service) => ( ].map((service) => (
<PublicSurface key={service.title} className="h-full p-6"> <PublicSurface key={service.title} className="h-full p-6 md:p-7">
<h3 className="text-xl font-semibold">{service.title}</h3> <h3 className="text-xl font-semibold">{service.title}</h3>
<p className="mt-3 leading-7 text-muted-foreground"> <p className="mt-3 leading-7 text-muted-foreground">
{service.body} {service.body}
@ -287,6 +337,95 @@ export function LocationLandingPage({
</div> </div>
</section> </section>
{isSaltLakeCity ? (
<section className="mx-auto mb-16 max-w-5xl space-y-6">
<PublicSurface className="p-6 md:p-8">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Why Rocky Mountain Vending
</p>
<h2 className="mt-3 text-3xl font-bold tracking-tight text-balance">
What Salt Lake City businesses usually want to verify before they choose a vendor.
</h2>
<p className="mt-4 text-base leading-relaxed text-muted-foreground">
Most businesses care about the same things: service speed,
product flexibility, local ownership, and whether the machines
feel modern and dependable after install.
</p>
<div className="mt-6 overflow-hidden rounded-[1.5rem] border border-border/60">
<div className="overflow-x-auto">
<table className="w-full min-w-[680px] border-collapse text-sm">
<thead className="bg-muted/55">
<tr className="border-b border-border/60">
<th className="px-4 py-3 text-left font-semibold text-foreground">
Comparison point
</th>
<th className="px-4 py-3 text-left font-semibold text-foreground">
Rocky Mountain Vending
</th>
<th className="px-4 py-3 text-left font-semibold text-foreground">
Small Vendor
</th>
<th className="px-4 py-3 text-left font-semibold text-foreground">
Large Vendor
</th>
</tr>
</thead>
<tbody>
{comparisonRows.map((row, index) => (
<tr
key={row[0]}
className={`border-b border-border/50 ${index % 2 === 0 ? "bg-background" : "bg-muted/20"}`}
>
<td className="px-4 py-3 font-medium text-foreground">
{row[0]}
</td>
<td className="px-4 py-3 text-muted-foreground">
{row[1]}
</td>
<td className="px-4 py-3 text-muted-foreground">
{row[2]}
</td>
<td className="px-4 py-3 text-muted-foreground">
{row[3]}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</PublicSurface>
<PublicSurface className="p-6 md:p-8">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Salt Lake City Services
</p>
<h2 className="mt-3 text-3xl font-bold tracking-tight text-balance">
The service paths Salt Lake City businesses usually ask about first.
</h2>
<div className="mt-6 grid gap-4 md:grid-cols-2">
{saltLakeServiceLinks.map((item) => (
<PublicInset key={item.title} className="flex h-full flex-col">
<h3 className="text-lg font-semibold text-foreground">
{item.title}
</h3>
<p className="mt-2 flex-1 text-sm leading-relaxed text-muted-foreground">
{item.body}
</p>
<Link
href={item.href}
className="mt-4 inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
>
Learn more
<ArrowRight className="h-4 w-4" />
</Link>
</PublicInset>
))}
</div>
</PublicSurface>
</section>
) : null}
<section className="mx-auto mb-16 grid max-w-5xl gap-6 lg:grid-cols-[1.05fr_0.95fr]"> <section className="mx-auto mb-16 grid max-w-5xl gap-6 lg:grid-cols-[1.05fr_0.95fr]">
<PublicSurface className="p-6 md:p-8"> <PublicSurface className="p-6 md:p-8">
<h2 className="text-3xl font-bold tracking-tight text-balance"> <h2 className="text-3xl font-bold tracking-tight text-balance">
@ -405,7 +544,7 @@ export function LocationLandingPage({
vending help you need. We&apos;ll follow up with the next best vending help you need. We&apos;ll follow up with the next best
option for your location. option for your location.
</p> </p>
<div className="mt-6 flex flex-col items-center gap-3"> <div className="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
<GetFreeMachineCta buttonLabel="See If Your Location Qualifies" /> <GetFreeMachineCta buttonLabel="See If Your Location Qualifies" />
<Link <Link
href="/contact-us#contact-form" href="/contact-us#contact-form"

View file

@ -28,9 +28,6 @@ import {
ShoppingCart, ShoppingCart,
LayoutGrid, LayoutGrid,
List, List,
ExternalLink,
Loader2,
AlertCircle,
} from "lucide-react" } from "lucide-react"
import type { Manual, ManualGroup } from "@/lib/manuals-types" import type { Manual, ManualGroup } from "@/lib/manuals-types"
import { getManualUrl, getThumbnailUrl } from "@/lib/manuals-types" import { getManualUrl, getThumbnailUrl } from "@/lib/manuals-types"
@ -40,210 +37,6 @@ import {
} from "@/lib/manuals-config" } from "@/lib/manuals-config"
import { ManualViewer } from "@/components/manual-viewer" import { ManualViewer } from "@/components/manual-viewer"
import { getManualsWithParts } from "@/lib/parts-lookup" import { getManualsWithParts } from "@/lib/parts-lookup"
import type { CachedEbayListing, EbayCacheState } from "@/lib/ebay-parts-match"
interface ProductSuggestionsResponse {
query: string
results: CachedEbayListing[]
cache: EbayCacheState
error?: string
}
interface ProductSuggestionsProps {
manual: Manual
className?: string
}
function ProductSuggestions({
manual,
className = "",
}: ProductSuggestionsProps) {
const [suggestions, setSuggestions] = useState<CachedEbayListing[]>([])
const [cache, setCache] = useState<EbayCacheState | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function loadSuggestions() {
setIsLoading(true)
setError(null)
try {
const query = [
manual.manufacturer,
manual.category,
manual.commonNames?.[0],
manual.searchTerms?.[0],
"vending machine",
]
.filter(Boolean)
.join(" ")
const params = new URLSearchParams({
keywords: query,
maxResults: "6",
sortOrder: "BestMatch",
})
const response = await fetch(`/api/ebay/search?${params.toString()}`)
const body = (await response.json().catch(() => null)) as
| ProductSuggestionsResponse
| null
if (!response.ok || !body) {
throw new Error(
body && typeof body.error === "string"
? body.error
: `Failed to load cached listings (${response.status})`
)
}
setSuggestions(Array.isArray(body.results) ? body.results : [])
setCache(body.cache || null)
setError(typeof body.error === "string" ? body.error : null)
} catch (err) {
console.error("Error loading product suggestions:", err)
setSuggestions([])
setCache(null)
setError(
err instanceof Error ? err.message : "Could not load product suggestions"
)
} finally {
setIsLoading(false)
}
}
if (manual) {
loadSuggestions()
}
}, [manual])
if (isLoading) {
return (
<div
className={`bg-white/60 dark:bg-yellow-900/20 rounded border border-yellow-300/30 dark:border-yellow-700/30 p-4 ${className}`}
>
<div className="flex items-center justify-center h-32">
<Loader2 className="h-5 w-5 text-yellow-700 dark:text-yellow-300 animate-spin" />
</div>
</div>
)
}
if (error) {
return (
<div
className={`bg-white/60 dark:bg-yellow-900/20 rounded border border-yellow-300/30 dark:border-yellow-700/30 p-4 ${className}`}
>
<div className="flex flex-col items-center justify-center gap-2 h-32 text-center">
<AlertCircle className="h-6 w-6 text-yellow-600" />
<span className="text-sm text-yellow-700 dark:text-yellow-200">
{error}
</span>
{cache?.lastSuccessfulAt ? (
<span className="text-[11px] text-yellow-600/80 dark:text-yellow-200/70">
Last refreshed {new Date(cache.lastSuccessfulAt).toLocaleString()}
</span>
) : null}
</div>
</div>
)
}
if (suggestions.length === 0) {
return (
<div
className={`bg-white/60 dark:bg-yellow-900/20 rounded border border-yellow-300/30 dark:border-yellow-700/30 p-4 ${className}`}
>
<div className="flex flex-col items-center justify-center gap-2 h-32 text-center">
<AlertCircle className="h-6 w-6 text-yellow-500" />
<span className="text-sm text-yellow-700 dark:text-yellow-200">
No cached eBay matches yet
</span>
<span className="text-[11px] text-yellow-600/80 dark:text-yellow-200/70">
{cache?.isStale
? "The background poll is behind, so this manual is showing the last known cache."
: "Try again after the next periodic cache refresh."}
</span>
</div>
</div>
)
}
return (
<div
className={`bg-white/60 dark:bg-yellow-900/20 rounded border border-yellow-300/30 dark:border-yellow-700/30 p-4 ${className}`}
>
<div className="flex items-center gap-2 mb-4">
<ShoppingCart className="h-4 w-4 text-yellow-700 dark:text-yellow-300" />
<h3 className="text-sm font-semibold text-yellow-900 dark:text-yellow-100">
Related Products
</h3>
</div>
{cache && (
<div className="mb-3 text-[11px] text-yellow-700/80 dark:text-yellow-200/70">
{cache.lastSuccessfulAt
? `Cache refreshed ${new Date(cache.lastSuccessfulAt).toLocaleString()}`
: "Cache is warming up"}
{cache.isStale ? " • stale cache" : ""}
</div>
)}
<div className="grid grid-cols-2 gap-3">
{suggestions.map((product) => (
<a
key={product.itemId}
href={product.affiliateLink}
target="_blank"
rel="noopener noreferrer"
className="block group"
>
<div className="bg-white dark:bg-yellow-900/30 rounded border border-yellow-300/40 dark:border-yellow-700/40 p-2 hover:bg-yellow-50 dark:hover:bg-yellow-900/40 transition-colors">
{/* Image */}
{product.imageUrl && (
<div className="mb-2 rounded overflow-hidden bg-yellow-100 dark:bg-yellow-900/50">
<img
src={product.imageUrl}
alt={product.title}
className="w-full h-16 object-cover"
onError={(e) => {
e.currentTarget.src = `https://via.placeholder.com/120x80/fbbf24/1f2937?text=${encodeURIComponent(product.title)}`
}}
/>
</div>
)}
{!product.imageUrl && (
<div className="mb-2 rounded overflow-hidden bg-yellow-100 dark:bg-yellow-900/50 h-16 flex items-center justify-center">
<span className="text-[10px] text-yellow-700 dark:text-yellow-300">
No Image
</span>
</div>
)}
{/* Product Details */}
<div className="space-y-1">
<div className="text-[11px] text-yellow-900 dark:text-yellow-100 line-clamp-2 min-h-[1.5rem]">
{product.title}
</div>
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-yellow-900 dark:text-yellow-100">
{product.price}
</span>
<ExternalLink className="h-3 w-3 text-yellow-700 dark:text-yellow-300 group-hover:text-yellow-900 dark:group-hover:text-yellow-100 transition-colors flex-shrink-0" />
</div>
{product.condition && (
<div className="text-[9px] text-yellow-700/80 dark:text-yellow-300/80">
{product.condition}
</div>
)}
</div>
</div>
</a>
))}
</div>
</div>
)
}
interface ManualsPageClientProps { interface ManualsPageClientProps {
manuals: Manual[] manuals: Manual[]
@ -272,7 +65,7 @@ function ManualCard({
const thumbnailUrl = getThumbnailUrl(manual) const thumbnailUrl = getThumbnailUrl(manual)
return ( return (
<Card className="overflow-hidden rounded-[1.75rem] border border-border/70 bg-background shadow-[0_18px_45px_rgba(15,23,42,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_24px_60px_rgba(15,23,42,0.12)]"> <Card className="overflow-hidden rounded-[1.75rem] border border-border/70 bg-background shadow-[0_16px_40px_rgba(15,23,42,0.075)] transition-all hover:-translate-y-0.5 hover:shadow-[0_22px_54px_rgba(15,23,42,0.11)]">
{thumbnailUrl && ( {thumbnailUrl && (
<div className="relative h-48 min-h-[192px] w-full overflow-hidden bg-[radial-gradient(circle_at_top_left,rgba(196,154,52,0.12),transparent_52%),linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,255,255,0.98))]"> <div className="relative h-48 min-h-[192px] w-full overflow-hidden bg-[radial-gradient(circle_at_top_left,rgba(196,154,52,0.12),transparent_52%),linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,255,255,0.98))]">
<Image <Image
@ -284,13 +77,13 @@ function ManualCard({
/> />
</div> </div>
)} )}
<CardHeader className="px-5 pt-5"> <CardHeader className="space-y-3 px-5 pt-5">
<CardTitle className="line-clamp-2 text-base leading-snug"> <CardTitle className="line-clamp-2 text-base leading-snug">
{manual.filename.replace(/\.pdf$/i, "")} {manual.filename.replace(/\.pdf$/i, "")}
</CardTitle> </CardTitle>
{manual.commonNames && manual.commonNames.length > 0 && ( {manual.commonNames && manual.commonNames.length > 0 && (
<div className="mt-2 flex flex-wrap gap-2"> <div className="mt-2 flex flex-wrap gap-2">
{manual.commonNames.map((name, index) => ( {manual.commonNames.slice(0, 3).map((name, index) => (
<Badge <Badge
key={index} key={index}
variant="secondary" variant="secondary"
@ -299,13 +92,21 @@ function ManualCard({
{name} {name}
</Badge> </Badge>
))} ))}
{manual.commonNames.length > 3 && (
<Badge
variant="secondary"
className="rounded-full border border-border/60 bg-muted/35 px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground"
>
+{manual.commonNames.length - 3} more
</Badge>
)}
</div> </div>
)} )}
{manual.searchTerms && {manual.searchTerms &&
manual.searchTerms.length > 0 && manual.searchTerms.length > 0 &&
!manual.commonNames && ( !manual.commonNames && (
<div className="mt-2 flex flex-wrap gap-2"> <div className="mt-2 flex flex-wrap gap-2">
{manual.searchTerms.map((term, index) => ( {manual.searchTerms.slice(0, 4).map((term, index) => (
<Badge <Badge
key={index} key={index}
variant="secondary" variant="secondary"
@ -314,6 +115,14 @@ function ManualCard({
{term} {term}
</Badge> </Badge>
))} ))}
{manual.searchTerms.length > 4 && (
<Badge
variant="secondary"
className="rounded-full border border-border/60 bg-muted/35 px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground"
>
+{manual.searchTerms.length - 4} more
</Badge>
)}
</div> </div>
)} )}
</CardHeader> </CardHeader>
@ -337,7 +146,7 @@ function ManualCard({
</Badge> </Badge>
)} )}
</div> </div>
<div className="text-sm text-muted-foreground space-y-1"> <div className="space-y-1.5 text-sm text-muted-foreground">
{showManufacturer && ( {showManufacturer && (
<p> <p>
<strong>Manufacturer:</strong> {manual.manufacturer} <strong>Manufacturer:</strong> {manual.manufacturer}
@ -681,9 +490,28 @@ export function ManualsPageClient({
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Search and Filter Controls */} {/* Search and Filter Controls */}
<PublicSurface> <PublicSurface className="p-4 md:p-6">
<CardContent className="p-0"> <CardContent className="p-0">
<div className="space-y-6"> <div className="space-y-5">
<div className="flex flex-col gap-4 rounded-[1.5rem] border border-border/60 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,249,240,0.94))] p-4 md:flex-row md:items-center md:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
Start With Search
</p>
<h2 className="mt-2 text-xl font-semibold tracking-tight text-foreground">
Find the manual first, then narrow it down
</h2>
<p className="mt-1 text-sm leading-relaxed text-muted-foreground">
Search by model, manufacturer, or category. Use filters if
you already know the brand or want manuals with parts.
</p>
</div>
<PublicInset className="w-full rounded-[1.25rem] px-4 py-3 text-sm text-muted-foreground shadow-none md:max-w-xs">
Showing <strong>{filteredManuals.length}</strong> of{" "}
<strong>{manuals.length}</strong> manuals
</PublicInset>
</div>
{/* Search Bar */} {/* Search Bar */}
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
@ -697,12 +525,14 @@ export function ManualsPageClient({
</div> </div>
{/* Filters Row */} {/* Filters Row */}
<div className="flex flex-col sm:flex-row flex-wrap gap-3 sm:gap-4 items-start sm:items-center"> <div className="rounded-[1.5rem] border border-border/60 bg-white/80 p-4">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2 w-full sm:w-auto"> <div className="flex items-center gap-2 w-full sm:w-auto">
<Filter className="h-4 w-4 text-muted-foreground flex-shrink-0" /> <Filter className="h-4 w-4 text-muted-foreground flex-shrink-0" />
<span className="text-sm font-medium">Filters:</span> <span className="text-sm font-medium">Filters:</span>
</div> </div>
<div className="flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-center">
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 w-full sm:w-auto"> <div className="flex flex-col sm:flex-row gap-3 sm:gap-4 w-full sm:w-auto">
<div className="relative"> <div className="relative">
<Select <Select
@ -800,10 +630,12 @@ export function ManualsPageClient({
Clear Filters Clear Filters
</Button> </Button>
)} )}
</div>
</div>
</div> </div>
{/* View Mode Toggle and Results Count */} {/* View Mode Toggle and Results Count */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4 border-t border-border/55 pt-1">
<div className="flex items-center gap-3 w-full sm:w-auto"> <div className="flex items-center gap-3 w-full sm:w-auto">
<span className="text-sm text-muted-foreground flex-shrink-0"> <span className="text-sm text-muted-foreground flex-shrink-0">
View: View:
@ -841,8 +673,8 @@ export function ManualsPageClient({
</div> </div>
<div className="text-sm text-muted-foreground w-full sm:w-auto text-center sm:text-left"> <div className="text-sm text-muted-foreground w-full sm:w-auto text-center sm:text-left">
Showing <strong>{filteredManuals.length}</strong> of{" "} View the full library grouped by manufacturer or switch to list
<strong>{manuals.length}</strong> manuals view for a faster scan.
</div> </div>
</div> </div>
</div> </div>
@ -870,11 +702,15 @@ export function ManualsPageClient({
{filteredGroupedManuals.map((group) => { {filteredGroupedManuals.map((group) => {
const organized = organizeCategories(group.categories) const organized = organizeCategories(group.categories)
return ( return (
<PublicSurface key={group.manufacturer} className="space-y-6"> <PublicSurface key={group.manufacturer} className="space-y-7 p-5 md:p-7">
<div className="border-b border-border/60 pb-3"> <div className="border-b border-border/60 pb-4">
<h2 className="text-2xl font-bold tracking-tight"> <h2 className="text-2xl font-bold tracking-tight">
{group.manufacturer} {group.manufacturer}
</h2> </h2>
<p className="mt-2 text-sm text-muted-foreground">
{Object.values(group.categories).flat().length} manuals
available from this manufacturer.
</p>
</div> </div>
{/* Machine Type Categories First */} {/* Machine Type Categories First */}
@ -912,7 +748,7 @@ export function ManualsPageClient({
</span> </span>
)} )}
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
{categoryManuals.map((manual) => ( {categoryManuals.map((manual) => (
<ManualCard <ManualCard
key={manual.path} key={manual.path}
@ -955,7 +791,7 @@ export function ManualsPageClient({
) )
</span> </span>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
{categoryManuals.map((manual) => ( {categoryManuals.map((manual) => (
<ManualCard <ManualCard
key={manual.path} key={manual.path}
@ -972,28 +808,13 @@ export function ManualsPageClient({
</div> </div>
)} )}
{/* Product Suggestions Section */}
{filteredManuals.length > 0 && (
<div className="space-y-6 mt-8">
<div className="border-t border-border/60 pt-6">
<h3 className="text-lg font-semibold text-muted-foreground mb-4">
Related Products
</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredManuals.slice(0, 3).map((manual) => (
<ProductSuggestions key={manual.path} manual={manual} />
))}
</div>
</div>
)}
</PublicSurface> </PublicSurface>
) )
})} })}
</div> </div>
) : ( ) : (
/* List View */ /* List View */
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
{filteredManuals.map((manual) => ( {filteredManuals.map((manual) => (
<ManualCard <ManualCard
key={manual.path} key={manual.path}

View file

@ -20,6 +20,11 @@ type PublicPageHeaderProps = {
children?: ReactNode children?: ReactNode
} }
type PublicProseProps = {
className?: string
children: ReactNode
}
export function PublicSection({ export function PublicSection({
id, id,
tone = "default", tone = "default",
@ -157,3 +162,16 @@ export function PublicSectionHeader({
</div> </div>
) )
} }
export function PublicProse({ className, children }: PublicProseProps) {
return (
<div
className={cn(
"prose prose-slate max-w-none prose-headings:font-semibold prose-headings:tracking-tight prose-headings:text-foreground prose-p:text-muted-foreground prose-p:leading-7 prose-li:text-muted-foreground prose-li:leading-7 prose-strong:text-foreground prose-a:text-foreground prose-a:decoration-primary/35 prose-a:underline-offset-4 hover:prose-a:decoration-primary prose-img:rounded-[1.5rem] prose-img:border prose-img:border-border/55 prose-img:shadow-[0_18px_45px_rgba(15,23,42,0.08)] prose-hr:border-border/50 prose-blockquote:border-primary/25 prose-blockquote:text-foreground md:prose-lg",
className
)}
>
{children}
</div>
)
}

View file

@ -9,6 +9,47 @@ import {
PublicSurface, PublicSurface,
} from "@/components/public-surface" } from "@/components/public-surface"
const reviewThemes = [
{
title: "Always cold and stocked",
body: "Customers consistently mention that the machines stay full, drinks stay cold, and the day-to-day experience feels dependable instead of neglected.",
},
{
title: "Fast, friendly service",
body: "When something needs attention, businesses talk about quick follow-through, easy communication, and issues getting handled without a long delay.",
},
{
title: "Wide product variety",
body: "Reviews regularly call out strong drink selection, snack variety, and the ability to request items that fit the people using the location.",
},
{
title: "Fair pricing and reliability",
body: "People mention competitive pricing, clean machines, and a setup that feels professional instead of frustrating or outdated.",
},
]
const featuredQuotes = [
{
quote:
"He is arguably one of the best vendors in the industry. There probably isn't too many people I would trust more than him.",
author: "Martin Harrison",
},
{
quote: "He always has my favorite energy drink at a great price!",
author: "TOPX Kingsford",
},
{
quote:
"This vending machine is my favorite at my job! It always works and has the best stuff!!",
author: "DJ Montoya",
},
{
quote:
"Great to work with, looking forward to having him in more locations.",
author: "Jennifer Spencer",
},
]
export function ReviewsPage() { export function ReviewsPage() {
useEffect(() => { useEffect(() => {
const existingScript = document.querySelector( const existingScript = document.querySelector(
@ -44,8 +85,65 @@ export function ReviewsPage() {
align="center" align="center"
eyebrow="Customer Reviews" eyebrow="Customer Reviews"
title="What Utah businesses say about working with Rocky Mountain Vending." title="What Utah businesses say about working with Rocky Mountain Vending."
description="Browse the live Google review feed and see what Utah businesses say about placement, restocking, repairs, and service." description="See the real themes customers mention most often, browse featured comments, and then dig into the live Google review feed."
/> >
<PublicInset className="mx-auto inline-flex w-fit rounded-full px-4 py-2 text-sm text-muted-foreground shadow-none">
Average rating: 4.9 out of 5, based on 50+ customer reviews.
</PublicInset>
</PublicPageHeader>
<section className="mt-12 grid gap-6 lg:grid-cols-[1.05fr_0.95fr]">
<PublicSurface>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Why Businesses Trust Rocky
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
The same strengths keep showing up in the reviews.
</h2>
<p className="mt-4 text-base leading-relaxed text-muted-foreground">
We&apos;re honored to have earned strong feedback from Utah
businesses that rely on us for placement, restocking, repairs, and
day-to-day service. The reviews tend to point back to the same
things: stocked machines, responsive help, a better product mix,
and follow-through after install.
</p>
<div className="mt-6 grid gap-4 md:grid-cols-2">
{reviewThemes.map((theme) => (
<PublicInset key={theme.title} className="h-full">
<h3 className="text-lg font-semibold text-foreground">
{theme.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{theme.body}
</p>
</PublicInset>
))}
</div>
</PublicSurface>
<PublicSurface className="flex flex-col justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Featured Comments
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
A few of the comments that capture the pattern.
</h2>
</div>
<div className="mt-6 grid gap-4">
{featuredQuotes.map((item) => (
<PublicInset key={item.author} className="p-5">
<p className="text-sm leading-relaxed text-foreground">
&ldquo;{item.quote}&rdquo;
</p>
<p className="mt-3 text-sm font-semibold text-primary">
{item.author}
</p>
</PublicInset>
))}
</div>
</PublicSurface>
</section>
<section className="mt-12"> <section className="mt-12">
<PublicSurface className="overflow-hidden p-5 md:p-7"> <PublicSurface className="overflow-hidden p-5 md:p-7">
@ -69,9 +167,9 @@ export function ReviewsPage() {
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 rounded-[1.5rem] bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,249,240,0.92))] p-2 sm:p-3">
<iframe <iframe
className="lc_reviews_widget min-h-[780px] w-full rounded-[1.5rem] border border-border/60 bg-background" className="lc_reviews_widget min-h-[620px] w-full rounded-[1.35rem] border border-border/60 bg-background md:min-h-[780px]"
src="https://reputationhub.site/reputation/widgets/review_widget/YAoWLgNSid8oG44j9BjG" src="https://reputationhub.site/reputation/widgets/review_widget/YAoWLgNSid8oG44j9BjG"
frameBorder="0" frameBorder="0"
scrolling="no" scrolling="no"
@ -143,30 +241,32 @@ export function ReviewsPage() {
<li>Should we ask about placement, machine sales, or direct service help?</li> <li>Should we ask about placement, machine sales, or direct service help?</li>
</ul> </ul>
</PublicInset> </PublicInset>
<Link <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-1">
href="/#request-machine" <Link
className="rounded-[1.5rem] border border-border/55 bg-background/70 p-5 text-left transition hover:border-primary/30 hover:text-primary" href="/#request-machine"
> className="rounded-[1.5rem] border border-border/55 bg-background/70 p-5 text-left transition hover:border-primary/30 hover:text-primary"
<h3 className="text-lg font-semibold text-foreground"> >
Free Placement <h3 className="text-lg font-semibold text-foreground">
</h3> Free Placement
<p className="mt-2 text-sm leading-relaxed text-muted-foreground"> </h3>
See whether your business qualifies for vending machine <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
placement and ongoing service. See whether your business qualifies for vending machine
</p> placement and ongoing service.
</Link> </p>
<Link </Link>
href="/contact-us#contact-form" <Link
className="rounded-[1.5rem] border border-border/55 bg-background/70 p-5 text-left transition hover:border-primary/30 hover:text-primary" href="/contact-us#contact-form"
> className="rounded-[1.5rem] border border-border/55 bg-background/70 p-5 text-left transition hover:border-primary/30 hover:text-primary"
<h3 className="text-lg font-semibold text-foreground"> >
Service or Sales <h3 className="text-lg font-semibold text-foreground">
</h3> Service or Sales
<p className="mt-2 text-sm leading-relaxed text-muted-foreground"> </h3>
Reach out about repairs, moving, manuals, parts, or machine <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
sales. Reach out about repairs, moving, manuals, parts, or machine
</p> sales.
</Link> </p>
</Link>
</div>
</div> </div>
<div className="mt-6 rounded-[1.5rem] border border-primary/12 bg-[linear-gradient(180deg,rgba(41,160,71,0.06),rgba(255,255,255,0.7))] p-5"> <div className="mt-6 rounded-[1.5rem] border border-primary/12 bg-[linear-gradient(180deg,rgba(41,160,71,0.06),rgba(255,255,255,0.7))] p-5">
<p className="text-sm font-semibold text-foreground"> <p className="text-sm font-semibold text-foreground">

View file

@ -74,7 +74,7 @@ export function WhoWeServePage({
} }
contentIntro={ contentIntro={
<> <>
<PublicInset className="h-full p-5 md:p-6"> <PublicInset className="h-full border-primary/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,251,245,0.94))] p-5 md:p-6">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
How We Tailor Service How We Tailor Service
</p> </p>
@ -87,7 +87,7 @@ export function WhoWeServePage({
<li>Service cadence adjusted so stocking and support stay consistent without adding staff work.</li> <li>Service cadence adjusted so stocking and support stay consistent without adding staff work.</li>
</ul> </ul>
</PublicInset> </PublicInset>
<PublicInset className="h-full p-5 md:p-6"> <PublicInset className="h-full border-border/50 bg-white/80 p-5 md:p-6">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Good Fit Signals Good Fit Signals
</p> </p>
@ -103,12 +103,12 @@ export function WhoWeServePage({
</> </>
} }
content={ content={
<div className="text-foreground">{content}</div> <div className="space-y-6 text-foreground">{content}</div>
} }
contentClassName="prose prose-lg max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-a:text-foreground prose-a:underline prose-a:decoration-primary/35 prose-a:underline-offset-4 hover:prose-a:decoration-primary prose-strong:text-foreground" contentClassName="prose-headings:mb-4 prose-headings:mt-10 prose-p:max-w-[68ch] prose-p:text-[1.02rem] prose-p:leading-8 prose-li:max-w-[68ch] prose-ul:space-y-2"
sections={ sections={
<section> <section>
<div className="mx-auto mb-6 max-w-3xl text-center"> <div className="mx-auto mb-8 max-w-3xl text-center">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Why Rocky Why Rocky
</p> </p>
@ -154,6 +154,12 @@ export function WhoWeServePage({
variant: "outline", variant: "outline",
}, },
], ],
note: (
<p className="text-sm leading-relaxed text-muted-foreground">
We can help you sort out whether this should start as a placement request,
a machine-sales conversation, or direct service support.
</p>
),
}} }}
/> />
) )