deploy: polish public marketing pages
This commit is contained in:
parent
9dfee33e49
commit
14cb8ce1fc
14 changed files with 714 additions and 153 deletions
|
|
@ -183,7 +183,8 @@
|
|||
transition:
|
||||
color 0.2s ease,
|
||||
text-decoration-color 0.2s ease,
|
||||
opacity 0.2s ease;
|
||||
opacity 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
|
|
@ -202,7 +203,8 @@
|
|||
transition:
|
||||
color 0.2s ease,
|
||||
text-decoration-color 0.2s ease,
|
||||
opacity 0.2s ease;
|
||||
opacity 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
a[href]:hover,
|
||||
|
|
@ -211,6 +213,29 @@
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
button a,
|
||||
[role="button"] a,
|
||||
.bg-primary a,
|
||||
.text-primary-foreground a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button a:hover,
|
||||
button a:focus,
|
||||
[role="button"] a:hover,
|
||||
[role="button"] a:focus,
|
||||
.bg-primary a:hover,
|
||||
.bg-primary a:focus,
|
||||
.text-primary-foreground a:hover,
|
||||
.text-primary-foreground a:focus {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
[role="button"]:focus-visible,
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ function LocationCard({
|
|||
}) {
|
||||
return (
|
||||
<Link href={href} className="group block h-full">
|
||||
<PublicSurface className="h-full p-5 transition-all hover:-translate-y-0.5 hover:shadow-[0_26px_65px_rgba(0,0,0,0.12)] md:p-6">
|
||||
<PublicInset className="h-full p-4 transition-all hover:-translate-y-0.5 hover:border-primary/25 hover:shadow-[0_20px_48px_rgba(0,0,0,0.09)] md:p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-xl font-semibold text-foreground transition-colors group-hover:text-primary">
|
||||
<h3 className="text-lg font-semibold text-foreground transition-colors group-hover:text-primary">
|
||||
{city}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">{zipCode}</p>
|
||||
|
|
@ -49,15 +49,15 @@ function LocationCard({
|
|||
<ArrowRight className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<PublicInset className="mt-5">
|
||||
<div className="mt-4 rounded-[1.1rem] border border-border/45 bg-background/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/75">
|
||||
Popular Areas
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
||||
{neighborhoods.slice(0, 2).join(", ")}
|
||||
</p>
|
||||
</PublicInset>
|
||||
</PublicSurface>
|
||||
</div>
|
||||
</PublicInset>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ export default function ServiceAreasPage() {
|
|||
</PublicSurface>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 space-y-12">
|
||||
<section className="mt-12 space-y-8">
|
||||
{[
|
||||
{
|
||||
title: "Salt Lake County",
|
||||
|
|
@ -243,19 +243,24 @@ export default function ServiceAreasPage() {
|
|||
items: utahCounty,
|
||||
},
|
||||
].map((section) => (
|
||||
<div key={section.title} className="space-y-5">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Coverage Area
|
||||
</p>
|
||||
<h2 className="mt-2 text-3xl font-semibold tracking-tight text-balance">
|
||||
{section.title}
|
||||
</h2>
|
||||
<p className="mt-2 max-w-3xl text-base leading-relaxed text-muted-foreground">
|
||||
{section.description}
|
||||
</p>
|
||||
<PublicSurface key={section.title} className="p-5 md:p-6">
|
||||
<div className="flex flex-col gap-4 border-b border-border/55 pb-5 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Coverage Area
|
||||
</p>
|
||||
<h2 className="mt-2 text-3xl font-semibold tracking-tight text-balance">
|
||||
{section.title}
|
||||
</h2>
|
||||
<p className="mt-2 max-w-3xl text-base leading-relaxed text-muted-foreground">
|
||||
{section.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-fit rounded-full border border-border/55 bg-background/70 px-3 py-1 text-sm text-muted-foreground">
|
||||
{section.items.length} cities
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{section.items.map((location) => (
|
||||
<LocationCard
|
||||
key={location.slug}
|
||||
|
|
@ -266,11 +271,11 @@ export default function ServiceAreasPage() {
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="mt-12 grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
|
||||
<section className="mt-12 grid gap-6 lg:grid-cols-2">
|
||||
<PublicSurface>
|
||||
<h2 className="text-3xl font-semibold tracking-tight text-balance">
|
||||
Why businesses choose Rocky Mountain Vending
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { notFound } from "next/navigation"
|
||||
import { loadImageMapping } from "@/lib/wordpress-content"
|
||||
import {
|
||||
generateRegistryMetadata,
|
||||
generateRegistryStructuredData,
|
||||
} from "@/lib/seo"
|
||||
import { getPageBySlug } from "@/lib/wordpress-data-loader"
|
||||
import { cleanWordPressContent } from "@/lib/clean-wordPress-content"
|
||||
import { FAQSection } from "@/components/faq-section"
|
||||
import { ServiceAreasSection } from "@/components/service-areas-section"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
|
|
@ -58,21 +56,13 @@ export default async function RepairsPage() {
|
|||
notFound()
|
||||
}
|
||||
|
||||
let imageMapping: any = {}
|
||||
try {
|
||||
imageMapping = loadImageMapping()
|
||||
} catch (e) {
|
||||
imageMapping = {}
|
||||
}
|
||||
|
||||
// Extract FAQs from content
|
||||
const faqs: Array<{ question: string; answer: string }> = []
|
||||
let contentWithoutFAQs = page.content || ""
|
||||
let contentWithoutVirtualServices = ""
|
||||
let virtualServicesContent = ""
|
||||
|
||||
if (page.content) {
|
||||
const contentStr = String(page.content)
|
||||
let strippedContent = contentStr
|
||||
|
||||
// Extract FAQ items from accordion structure
|
||||
const questionMatches = contentStr.matchAll(
|
||||
|
|
@ -112,40 +102,28 @@ export default async function RepairsPage() {
|
|||
if (faqs.length > 0) {
|
||||
const faqSectionRegex =
|
||||
/<h2[^>]*>.*?Answers\s+To\s+Common\s+Questions.*?<\/h2>[\s\S]*?(?=<h2[^>]*>.*?Virtual\s+Services|<h2[^>]*>.*?Service\s+Area|$)/i
|
||||
contentWithoutFAQs = contentStr.replace(faqSectionRegex, "").trim()
|
||||
strippedContent = contentStr.replace(faqSectionRegex, "").trim()
|
||||
}
|
||||
|
||||
// Extract Virtual Services section
|
||||
const virtualServicesRegex =
|
||||
/<h2[^>]*>.*?Virtual\s+Services.*?<\/h2>([\s\S]*?)(?=<h2[^>]*>.*?Service\s+Area|$)/i
|
||||
const virtualMatch = contentStr.match(virtualServicesRegex)
|
||||
const virtualMatch = strippedContent.match(virtualServicesRegex)
|
||||
if (virtualMatch) {
|
||||
virtualServicesContent = virtualMatch[1]
|
||||
// Remove Virtual Services from main content
|
||||
contentWithoutVirtualServices = contentWithoutFAQs
|
||||
.replace(virtualServicesRegex, "")
|
||||
.trim()
|
||||
} else {
|
||||
contentWithoutVirtualServices = contentWithoutFAQs
|
||||
}
|
||||
}
|
||||
|
||||
const content = contentWithoutVirtualServices ? (
|
||||
<div className="max-w-none">
|
||||
{cleanWordPressContent(String(contentWithoutVirtualServices), {
|
||||
imageMapping,
|
||||
pageTitle: page.title,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No content available.</p>
|
||||
)
|
||||
|
||||
const structuredData = generateRegistryStructuredData("repairs", {
|
||||
datePublished: page.date,
|
||||
dateModified: page.modified || page.date,
|
||||
})
|
||||
|
||||
const excerpt = String(page.excerpt || "")
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
|
||||
const surfaceCardClass =
|
||||
"rounded-[var(--public-surface-radius)] border border-border/70 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,251,243,0.96))] shadow-[var(--public-surface-shadow)]"
|
||||
const insetCardClass =
|
||||
|
|
@ -171,37 +149,14 @@ export default async function RepairsPage() {
|
|||
align="center"
|
||||
className="mb-8"
|
||||
eyebrow="Repair Services"
|
||||
title={page.title || "Vending Machine Repairs and Service"}
|
||||
title="Vending machine repairs and service for Utah businesses"
|
||||
description={
|
||||
"Rocky Mountain Vending delivers expert vending machine repair and maintenance services to keep your business thriving."
|
||||
"Get help with payment issues, refrigeration problems, machine errors, and ongoing maintenance from a local vending service team."
|
||||
}
|
||||
>
|
||||
<p className="mx-auto max-w-3xl text-base leading-relaxed text-muted-foreground md:text-lg">
|
||||
Rocky Mountain Vending delivers expert{" "}
|
||||
<Link
|
||||
href="/services/repairs"
|
||||
className="text-primary hover:underline font-semibold"
|
||||
>
|
||||
vending machine repair
|
||||
</Link>{" "}
|
||||
and maintenance services to keep your business thriving. From
|
||||
resolving jammed coin slots and refrigeration issues to fixing
|
||||
non-dispensing machines, our skilled technicians ensure reliable
|
||||
performance. For all your{" "}
|
||||
<Link
|
||||
href="/services/parts"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
vending machine parts
|
||||
</Link>{" "}
|
||||
needs and professional{" "}
|
||||
<Link
|
||||
href="/services/moving"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
vending machine moving
|
||||
</Link>{" "}
|
||||
services, contact us today for fast, professional solutions!
|
||||
{excerpt ||
|
||||
"Rocky Mountain Vending helps businesses across Davis, Salt Lake, and Utah counties keep machines running with practical repair, maintenance, and support guidance."}
|
||||
</p>
|
||||
</PublicPageHeader>
|
||||
{/* Images Carousel */}
|
||||
|
|
@ -211,15 +166,71 @@ export default async function RepairsPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{contentWithoutVirtualServices ? (
|
||||
<section className="py-16 md:py-20 bg-background">
|
||||
<div className="container mx-auto px-4 max-w-4xl">
|
||||
<PublicSurface>
|
||||
<div className="max-w-none">{content}</div>
|
||||
</PublicSurface>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
<section className="py-16 md:py-20 bg-background">
|
||||
<div className="container mx-auto grid max-w-6xl gap-6 px-4 lg:grid-cols-[1.08fr_0.92fr]">
|
||||
<PublicSurface className="h-full">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Repair Overview
|
||||
</p>
|
||||
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
|
||||
Clear next steps when a machine is down, rejecting payments, or
|
||||
needs service.
|
||||
</h2>
|
||||
<p className="mt-4 text-base leading-7 text-muted-foreground md:text-lg md:leading-8">
|
||||
We help with common vending issues like bill acceptor problems,
|
||||
refrigeration failures, card reader troubleshooting, controller
|
||||
errors, and recurring maintenance needs. If the issue can be
|
||||
handled virtually, we can talk through that too.
|
||||
</p>
|
||||
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
|
||||
<Link
|
||||
href="#request-service"
|
||||
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:opacity-95"
|
||||
>
|
||||
Request Service
|
||||
</Link>
|
||||
<Link
|
||||
href="/services/parts"
|
||||
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-background px-5 text-sm font-medium text-foreground transition hover:border-primary/35 hover:text-primary"
|
||||
>
|
||||
Need Parts Instead?
|
||||
</Link>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
<PublicInset className="h-full p-5 md:p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Before You Reach Out
|
||||
</p>
|
||||
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance">
|
||||
The more detail you share, the faster we can point you in the
|
||||
right direction.
|
||||
</h2>
|
||||
<ul className="mt-5 space-y-3">
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-primary" />
|
||||
<span className="text-sm leading-6 text-muted-foreground">
|
||||
Include the machine model, brand, and whether the issue is
|
||||
intermittent or constant.
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-primary" />
|
||||
<span className="text-sm leading-6 text-muted-foreground">
|
||||
Tell us if the problem is payment-related, refrigeration,
|
||||
dispensing, display errors, or a recent setup change.
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-primary" />
|
||||
<span className="text-sm leading-6 text-muted-foreground">
|
||||
Photos or short videos can make remote triage much easier
|
||||
before an on-site visit is scheduled.
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</PublicInset>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Services Section */}
|
||||
<section className="py-20 md:py-28 bg-muted/30">
|
||||
|
|
|
|||
|
|
@ -1,16 +1,74 @@
|
|||
import { notFound } from "next/navigation"
|
||||
import { loadImageMapping } from "@/lib/wordpress-content"
|
||||
import type { ImageMapping } from "@/lib/wordpress-content"
|
||||
import type { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
CheckCircle2,
|
||||
CreditCard,
|
||||
Refrigerator,
|
||||
ShoppingCart,
|
||||
} from "lucide-react"
|
||||
import { generateSEOMetadata, generateStructuredData } from "@/lib/seo"
|
||||
import { getPageBySlug } from "@/lib/wordpress-data-loader"
|
||||
import { cleanWordPressContent } from "@/lib/clean-wordPress-content"
|
||||
import type { Metadata } from "next"
|
||||
import { PublicPageHeader, PublicSurface } from "@/components/public-surface"
|
||||
import {
|
||||
PublicInset,
|
||||
PublicPageHeader,
|
||||
PublicSectionHeader,
|
||||
PublicSurface,
|
||||
} from "@/components/public-surface"
|
||||
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
const WORDPRESS_SLUG = "vending-machines-for-sale-in-utah"
|
||||
|
||||
const machineHighlights = [
|
||||
"Snack, drink, combo, and card-reader-ready equipment options",
|
||||
"New and used machine guidance based on traffic, budget, and product mix",
|
||||
"Local help with payment hardware, installation planning, and next-step questions",
|
||||
]
|
||||
|
||||
const machineOptions = [
|
||||
{
|
||||
title: "Snack and drink machines",
|
||||
description:
|
||||
"Traditional snack, beverage, and combo machines for breakrooms, customer spaces, and mixed-traffic locations.",
|
||||
icon: ShoppingCart,
|
||||
},
|
||||
{
|
||||
title: "Cashless payment hardware",
|
||||
description:
|
||||
"Card reader and mobile-payment options that help modernize older machines or support new installs.",
|
||||
icon: CreditCard,
|
||||
},
|
||||
{
|
||||
title: "Refrigerated equipment",
|
||||
description:
|
||||
"Cold drink and refrigerated machine options for workplaces that need dependable temperature-controlled service.",
|
||||
icon: Refrigerator,
|
||||
},
|
||||
]
|
||||
|
||||
const buyingSteps = [
|
||||
{
|
||||
title: "Tell us about the location",
|
||||
body: "We learn about traffic, available space, product needs, and whether you are comparing free placement with a direct purchase.",
|
||||
},
|
||||
{
|
||||
title: "Compare the right machine options",
|
||||
body: "We help narrow down machine styles, payment hardware, and new-versus-used tradeoffs so you are looking at realistic fits.",
|
||||
},
|
||||
{
|
||||
title: "Plan install and support",
|
||||
body: "Once you know what you want, we can talk through delivery, setup, payment configuration, and any follow-up service needs.",
|
||||
},
|
||||
]
|
||||
|
||||
function normalizeWpImageUrl(url?: string) {
|
||||
if (!url) return null
|
||||
return url.replace("https:///", "https://rockymountainvending.com/")
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const page = getPageBySlug(WORDPRESS_SLUG)
|
||||
|
||||
|
|
@ -39,13 +97,6 @@ export default async function MachinesForSalePage() {
|
|||
notFound()
|
||||
}
|
||||
|
||||
let imageMapping: ImageMapping = {}
|
||||
try {
|
||||
imageMapping = loadImageMapping()
|
||||
} catch {
|
||||
imageMapping = {}
|
||||
}
|
||||
|
||||
const structuredData = (() => {
|
||||
try {
|
||||
return generateStructuredData({
|
||||
|
|
@ -70,6 +121,16 @@ export default async function MachinesForSalePage() {
|
|||
}
|
||||
})()
|
||||
|
||||
const heroImage =
|
||||
normalizeWpImageUrl(page.images?.[0]?.url) ??
|
||||
"https://rockymountainvending.com/wp-content/uploads/2024/01/EH0A1551-HDR.webp"
|
||||
const comboImage =
|
||||
normalizeWpImageUrl(page.images?.[1]?.url) ??
|
||||
"https://rockymountainvending.com/wp-content/uploads/2022/06/Seage-HY900-Combo.webp"
|
||||
const paymentImage =
|
||||
normalizeWpImageUrl(page.images?.[2]?.url) ??
|
||||
"https://rockymountainvending.com/wp-content/uploads/2024/01/Parlevel-Pay-Plus.jpg"
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
|
|
@ -88,19 +149,168 @@ export default async function MachinesForSalePage() {
|
|||
]}
|
||||
/>
|
||||
<PublicPageHeader
|
||||
align="center"
|
||||
eyebrow="Machine Sales"
|
||||
title={page.title || "Vending Machines for Sale in Utah"}
|
||||
description="Compare machine options, payment hardware, and support with help from the Rocky Mountain Vending team."
|
||||
/>
|
||||
title="Compare vending machines, payment hardware, and purchase options with a local Utah team."
|
||||
description="If you are looking at buying equipment instead of free placement, we can help you compare machine styles, payment systems, and next-step support without sending you through a generic catalog dump."
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||
<Button asChild size="lg" className="min-h-11 rounded-full px-6">
|
||||
<Link href="/contact-us#contact-form">Ask About Sales</Link>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="min-h-11 rounded-full px-6"
|
||||
>
|
||||
<Link href="/products">Browse Product Listings</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</PublicPageHeader>
|
||||
|
||||
<PublicSurface className="mt-10">
|
||||
<div className="max-w-none">
|
||||
{cleanWordPressContent(String(page.content || ""), {
|
||||
imageMapping,
|
||||
pageTitle: page.title,
|
||||
<section className="mt-10 grid gap-6 xl:grid-cols-[1.05fr_0.95fr]">
|
||||
<PublicSurface className="flex h-full flex-col justify-center">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Sales Overview
|
||||
</p>
|
||||
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
|
||||
Buying a machine should feel clear before you spend money.
|
||||
</h2>
|
||||
<p className="mt-4 text-base leading-7 text-muted-foreground md:text-lg md:leading-8">
|
||||
We help Utah businesses sort through machine type, payment
|
||||
hardware, and install considerations so you can decide whether a
|
||||
direct purchase is the right move for your location.
|
||||
</p>
|
||||
<ul className="mt-6 space-y-3">
|
||||
{machineHighlights.map((highlight) => (
|
||||
<li key={highlight} className="flex items-start gap-3">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-primary" />
|
||||
<span className="text-sm leading-6 text-foreground md:text-base">
|
||||
{highlight}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PublicSurface>
|
||||
<div className="relative min-h-[320px] overflow-hidden rounded-[var(--public-surface-radius)] border border-border/65 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,249,240,0.96))] p-3 shadow-[0_20px_52px_rgba(15,23,42,0.075)]">
|
||||
<Image
|
||||
src={heroImage}
|
||||
alt="Vending machine option available for sale in Utah"
|
||||
fill
|
||||
className="rounded-[calc(var(--public-surface-radius)-0.45rem)] object-cover"
|
||||
sizes="(max-width: 1280px) 100vw, 560px"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<PublicSectionHeader
|
||||
eyebrow="Machine Options"
|
||||
title="What businesses usually want help comparing"
|
||||
description="Most sales conversations come down to the machine type, payment setup, and whether a direct purchase makes more sense than placement."
|
||||
className="mx-auto mb-8 max-w-3xl text-center"
|
||||
/>
|
||||
<div className="grid gap-4 lg:grid-cols-3">
|
||||
{machineOptions.map((option) => {
|
||||
const Icon = option.icon
|
||||
return (
|
||||
<PublicInset key={option.title} className="h-full p-5 md:p-6">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<h3 className="mt-4 text-xl font-semibold tracking-tight text-foreground">
|
||||
{option.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground md:text-base">
|
||||
{option.description}
|
||||
</p>
|
||||
</PublicInset>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 grid gap-6 lg:grid-cols-[0.98fr_1.02fr]">
|
||||
<PublicSurface className="overflow-hidden p-0">
|
||||
<div className="grid gap-0 md:grid-cols-2">
|
||||
<div className="relative min-h-[260px]">
|
||||
<Image
|
||||
src={comboImage}
|
||||
alt="Combo vending machine for sale"
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 768px) 100vw, 360px"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center p-5 md:p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
New vs. Used
|
||||
</p>
|
||||
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance">
|
||||
We can help you sort through budget, features, and condition.
|
||||
</h2>
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground md:text-base">
|
||||
Some buyers need dependable starter equipment. Others need a
|
||||
cleaner, more modern machine with stronger payment support.
|
||||
We can talk through both without pushing you into the wrong
|
||||
setup.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
<PublicSurface className="overflow-hidden p-0">
|
||||
<div className="grid gap-0 md:grid-cols-2">
|
||||
<div className="order-2 flex flex-col justify-center p-5 md:order-1 md:p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Payment Hardware
|
||||
</p>
|
||||
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance">
|
||||
Card readers and cashless upgrades are often part of the decision.
|
||||
</h2>
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground md:text-base">
|
||||
If you are trying to modernize how people pay, we can help
|
||||
you think through card readers, mobile payments, and
|
||||
compatibility before you commit to a machine.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative order-1 min-h-[260px] md:order-2">
|
||||
<Image
|
||||
src={paymentImage}
|
||||
alt="Cashless payment hardware for vending machines"
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 768px) 100vw, 360px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<PublicSectionHeader
|
||||
eyebrow="Buying Process"
|
||||
title="A simpler way to move from questions to a real option"
|
||||
description="You do not need to have the exact model picked out before you reach out. Most of the work is narrowing to the right fit."
|
||||
className="mx-auto mb-8 max-w-3xl text-center"
|
||||
/>
|
||||
<div className="grid gap-4 lg:grid-cols-3">
|
||||
{buyingSteps.map((step, index) => (
|
||||
<PublicInset key={step.title} className="h-full p-5 md:p-6">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary text-lg font-semibold text-primary-foreground">
|
||||
{index + 1}
|
||||
</div>
|
||||
<h3 className="mt-4 text-xl font-semibold tracking-tight text-foreground">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground md:text-base">
|
||||
{step.body}
|
||||
</p>
|
||||
</PublicInset>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
|
||||
<PublicSurface>
|
||||
|
|
@ -111,9 +321,9 @@ export default async function MachinesForSalePage() {
|
|||
Need a free machine instead of buying one?
|
||||
</h2>
|
||||
<p className="mt-3 text-base leading-relaxed text-muted-foreground">
|
||||
If you're a business looking for placement instead of a
|
||||
purchase, we can help you find the right setup for your
|
||||
location.
|
||||
If you are a business looking for placement instead of a
|
||||
purchase, we can help you figure out whether your location is a
|
||||
fit before you spend money on equipment.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<GetFreeMachineCta buttonLabel="Get Free Placement" />
|
||||
|
|
@ -125,13 +335,23 @@ export default async function MachinesForSalePage() {
|
|||
Need Sales Help?
|
||||
</p>
|
||||
<h3 className="mt-3 text-2xl font-semibold tracking-tight text-balance">
|
||||
Talk through machine sales, placement, or feature questions.
|
||||
Talk through machine sales, placement, or payment questions.
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||||
We can help with new vs. used options, payment hardware, and
|
||||
whether free placement or a direct purchase makes more sense
|
||||
for your location.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="min-h-11 rounded-full px-6"
|
||||
>
|
||||
<Link href="/contact-us#contact-form">Talk to Sales</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
|
||||
export function AboutPage() {
|
||||
return (
|
||||
<div className="public-page max-w-6xl">
|
||||
<div className="public-page">
|
||||
<Breadcrumbs
|
||||
className="mb-6"
|
||||
items={[{ label: "About Us", href: "/about-us" }]}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,37 @@ export function ContactPage() {
|
|||
description="Use the form for repairs, moving, manuals, machine sales, or general questions. If you'd rather talk now, call us during business hours."
|
||||
/>
|
||||
|
||||
<div className="mt-10 grid gap-8 lg:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)] lg:items-start">
|
||||
<section className="mt-10 grid gap-4 lg:grid-cols-3">
|
||||
<PublicInset className="h-full p-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
|
||||
Repairs
|
||||
</p>
|
||||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||||
Include the machine model, what the machine is doing, and any photos
|
||||
or videos that can help us triage the issue faster.
|
||||
</p>
|
||||
</PublicInset>
|
||||
<PublicInset className="h-full p-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
|
||||
Sales or Placement
|
||||
</p>
|
||||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||||
Tell us about your location, traffic, and whether you are asking
|
||||
about free placement, machine sales, or both.
|
||||
</p>
|
||||
</PublicInset>
|
||||
<PublicInset className="h-full p-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
|
||||
Manuals or Parts
|
||||
</p>
|
||||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||||
Share the machine brand and model so we can point you toward the
|
||||
right part, manual, or support path.
|
||||
</p>
|
||||
</PublicInset>
|
||||
</section>
|
||||
|
||||
<div className="mt-6 grid gap-6 lg:grid-cols-[minmax(0,1.1fr)_minmax(320px,0.9fr)] lg:items-start">
|
||||
<PublicSurface id="contact-form" as="section" className="p-5 md:p-7">
|
||||
<div className="mb-6 flex flex-wrap items-center gap-3">
|
||||
<div className="rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-primary">
|
||||
|
|
@ -45,7 +75,21 @@ export function ContactPage() {
|
|||
/>
|
||||
</PublicSurface>
|
||||
|
||||
<aside className="space-y-5">
|
||||
<aside className="space-y-4 lg:sticky lg:top-28">
|
||||
<PublicSurface className="p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
|
||||
Quick Guidance
|
||||
</p>
|
||||
<h2 className="mt-2 text-2xl font-semibold text-foreground">
|
||||
We'll route you to the right next step
|
||||
</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
||||
If you are not sure whether to ask about placement, repairs,
|
||||
moving, manuals, or sales, that's fine. Send the details you
|
||||
have and we'll help sort it out.
|
||||
</p>
|
||||
</PublicSurface>
|
||||
|
||||
<PublicSurface className="overflow-hidden p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
|
||||
Direct Options
|
||||
|
|
@ -59,10 +103,10 @@ export function ContactPage() {
|
|||
below.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="mt-5 space-y-3">
|
||||
<a
|
||||
href={businessConfig.publicCallUrl}
|
||||
className="flex items-start gap-4 rounded-2xl border border-border/60 bg-white px-4 py-4 transition hover:border-primary/35"
|
||||
className="flex items-start gap-4 rounded-2xl border border-border/55 bg-background/65 px-4 py-4 transition hover:border-primary/35"
|
||||
>
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<Phone className="h-5 w-5" />
|
||||
|
|
@ -80,7 +124,7 @@ export function ContactPage() {
|
|||
|
||||
<a
|
||||
href={`mailto:${businessConfig.email}?Subject=Rocky%20Mountain%20Vending%20Inquiry`}
|
||||
className="flex items-start gap-4 rounded-2xl border border-border/60 bg-white px-4 py-4 transition hover:border-primary/35"
|
||||
className="flex items-start gap-4 rounded-2xl border border-border/55 bg-background/65 px-4 py-4 transition hover:border-primary/35"
|
||||
>
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<Mail className="h-5 w-5" />
|
||||
|
|
@ -113,7 +157,7 @@ export function ContactPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 space-y-2">
|
||||
<div className="mt-4 space-y-2">
|
||||
{businessHours.map((schedule) => (
|
||||
<PublicInset
|
||||
key={schedule.day}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ interface DropdownPageShellProps {
|
|||
title: string
|
||||
description?: string
|
||||
headerContent?: ReactNode
|
||||
contentIntro?: ReactNode
|
||||
content: ReactNode
|
||||
contentClassName?: string
|
||||
contentSurfaceClassName?: string
|
||||
|
|
@ -41,6 +42,7 @@ export function DropdownPageShell({
|
|||
title,
|
||||
description,
|
||||
headerContent,
|
||||
contentIntro,
|
||||
content,
|
||||
contentClassName,
|
||||
contentSurfaceClassName,
|
||||
|
|
@ -61,11 +63,32 @@ export function DropdownPageShell({
|
|||
{headerContent}
|
||||
</PublicPageHeader>
|
||||
|
||||
<section className="mt-10">
|
||||
{contentIntro ? (
|
||||
<section className="mt-10 grid gap-4 lg:grid-cols-2">{contentIntro}</section>
|
||||
) : null}
|
||||
|
||||
<section className={cn(contentIntro ? "mt-6" : "mt-10")}>
|
||||
<PublicSurface
|
||||
className={cn("overflow-hidden", contentSurfaceClassName)}
|
||||
className={cn(
|
||||
"relative overflow-hidden p-0 md:p-0",
|
||||
contentSurfaceClassName
|
||||
)}
|
||||
>
|
||||
<div className={cn("max-w-none", contentClassName)}>{content}</div>
|
||||
<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="relative p-5 md:p-7 lg:p-9">
|
||||
<div className="mb-6 flex items-center justify-between gap-4 border-b border-border/55 pb-4">
|
||||
<div>
|
||||
<p className="text-[0.72rem] font-semibold uppercase tracking-[0.22em] text-primary/80">
|
||||
Location Guide
|
||||
</p>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||
How Rocky Mountain Vending typically approaches this type of
|
||||
location, from placement fit to service expectations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("mx-auto max-w-3xl", contentClassName)}>{content}</div>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
||||
|
|
@ -73,7 +96,8 @@ export function DropdownPageShell({
|
|||
|
||||
{cta ? (
|
||||
<section className="mt-12">
|
||||
<PublicSurface className="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%)]" />
|
||||
{cta.eyebrow ? (
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
{cta.eyebrow}
|
||||
|
|
@ -99,7 +123,7 @@ export function DropdownPageShell({
|
|||
))}
|
||||
</div>
|
||||
{cta.note ? (
|
||||
<PublicInset className="mx-auto mt-6 max-w-2xl text-left sm:text-center">
|
||||
<PublicInset className="mx-auto mt-6 max-w-2xl border-primary/10 text-left sm:text-center">
|
||||
{cta.note}
|
||||
</PublicInset>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ const features = [
|
|||
title: "Free Placement",
|
||||
description:
|
||||
"For qualifying locations, we can place the machines, stock them, and stay responsible for day-to-day service after install.",
|
||||
link: "/about-us",
|
||||
link: "/#how-it-works",
|
||||
linkText: "How placement works",
|
||||
},
|
||||
{
|
||||
icon: Wrench,
|
||||
title: "Repairs and Service",
|
||||
title: "Repairs and Services",
|
||||
description:
|
||||
"We handle repairs, restocking, and routine service so your team does not have to manage the machines.",
|
||||
link: "/services/repairs",
|
||||
link: "/services/repairs#request-service",
|
||||
linkText: "Repair services",
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function Footer() {
|
|||
<div className="mx-auto w-full max-w-[var(--public-shell-max)] px-4 py-14 sm:px-5 md:py-20 lg:px-6">
|
||||
<div className="grid gap-6 lg:grid-cols-[1.2fr_0.9fr_0.9fr_1fr]">
|
||||
{/* Company Info */}
|
||||
<div className="rounded-[2rem] border border-border/60 bg-white/92 p-6 shadow-[0_18px_48px_rgba(15,23,42,0.07)] lg:col-span-1">
|
||||
<div className="px-6 py-5 lg:col-span-1">
|
||||
<Link href="/" className="inline-flex">
|
||||
<Image
|
||||
src="/rmv-logo.png"
|
||||
|
|
@ -104,7 +104,7 @@ export function Footer() {
|
|||
</div>
|
||||
|
||||
{/* Services */}
|
||||
<div className="footer-section rounded-[2rem] border border-border/60 bg-white/88 px-5 py-5 shadow-[0_14px_38px_rgba(15,23,42,0.06)]">
|
||||
<div className="px-5 py-5">
|
||||
<h3 className="font-semibold mb-5 text-base">Services</h3>
|
||||
<ul className="space-y-3 text-sm text-muted-foreground">
|
||||
<li>
|
||||
|
|
@ -159,7 +159,7 @@ export function Footer() {
|
|||
</div>
|
||||
|
||||
{/* Company */}
|
||||
<div className="footer-section rounded-[2rem] border border-border/60 bg-white/88 px-5 py-5 shadow-[0_14px_38px_rgba(15,23,42,0.06)]">
|
||||
<div className="px-5 py-5">
|
||||
<h3 className="font-semibold mb-5 text-base">Company</h3>
|
||||
<ul className="space-y-3 text-sm text-muted-foreground">
|
||||
<li>
|
||||
|
|
@ -198,7 +198,7 @@ export function Footer() {
|
|||
</div>
|
||||
|
||||
{/* Service Areas */}
|
||||
<div className="footer-section rounded-[2rem] border border-border/60 bg-white/88 px-5 py-5 shadow-[0_14px_38px_rgba(15,23,42,0.06)]">
|
||||
<div className="px-5 py-5">
|
||||
<h3 className="font-semibold mb-5 text-base">Service Areas</h3>
|
||||
<ul className="space-y-3 text-sm text-muted-foreground">
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -162,9 +162,16 @@ export function Header() {
|
|||
{ label: "Reviews", href: "/reviews" },
|
||||
{ label: "FAQs", href: "/about/faqs" },
|
||||
]
|
||||
const moreItems = [
|
||||
{ label: "Food & Beverage", href: "/food-and-beverage/healthy-options" },
|
||||
{ label: "Blog Posts", href: "/blog" },
|
||||
{ label: "About Us", href: "/about-us" },
|
||||
{ label: "Products", href: "/products" },
|
||||
{ label: "Service Areas", href: "/service-areas" },
|
||||
]
|
||||
|
||||
const desktopLinkClassName =
|
||||
"rounded-full px-3 py-2 text-sm font-medium text-foreground transition hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15"
|
||||
"inline-flex items-center whitespace-nowrap rounded-full px-2.5 py-2 text-[0.95rem] font-medium text-foreground transition hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15 lg:px-3 lg:text-sm"
|
||||
const mobileLinkClassName =
|
||||
"flex min-h-11 items-center rounded-[1rem] px-4 text-sm font-medium text-foreground transition hover:bg-primary/6 hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15"
|
||||
const mobileGroupButtonClassName =
|
||||
|
|
@ -175,7 +182,7 @@ export function Header() {
|
|||
return (
|
||||
<header className="sticky top-0 z-40 w-full border-b border-border/50 bg-white/92 shadow-[0_10px_35px_rgba(15,23,42,0.06)] backdrop-blur supports-[backdrop-filter]:bg-white/80">
|
||||
<div className="mx-auto w-full max-w-[var(--public-shell-max)] px-4 sm:px-5 lg:px-6">
|
||||
<div className="flex h-[var(--header-height)] items-center justify-between gap-3 lg:gap-6">
|
||||
<div className="flex h-[var(--header-height)] items-center justify-between gap-3 md:hidden lg:gap-6">
|
||||
{/* Logo */}
|
||||
<Link
|
||||
href="/"
|
||||
|
|
@ -192,7 +199,7 @@ export function Header() {
|
|||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden flex-1 items-center justify-center gap-1 md:flex lg:gap-2">
|
||||
<nav className="hidden flex-1 items-center justify-center gap-1 2xl:flex 2xl:gap-2">
|
||||
<Link href="/" className={desktopLinkClassName}>
|
||||
Home
|
||||
</Link>
|
||||
|
|
@ -371,7 +378,7 @@ export function Header() {
|
|||
</nav>
|
||||
|
||||
{/* Desktop CTA */}
|
||||
<div className="hidden flex-shrink-0 items-center gap-2 md:flex lg:gap-3">
|
||||
<div className="hidden flex-shrink-0 items-center gap-2 2xl:flex 2xl:gap-3">
|
||||
<CartButton
|
||||
onClick={() => dispatch({ type: "SET_CART", value: true })}
|
||||
/>
|
||||
|
|
@ -393,7 +400,7 @@ export function Header() {
|
|||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-border/60 bg-white text-foreground transition hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15 md:hidden"
|
||||
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-border/60 bg-white text-foreground transition hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15 2xl:hidden"
|
||||
onClick={() => dispatch({ type: "TOGGLE_MENU" })}
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded={state.isMenuOpen}
|
||||
|
|
@ -406,6 +413,161 @@ export function Header() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:block">
|
||||
<div className="flex min-h-[4.75rem] items-center justify-between gap-4 py-3">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex min-w-0 flex-shrink-0 items-center gap-2 rounded-full"
|
||||
>
|
||||
<Image
|
||||
src="/rmv-logo.png"
|
||||
alt="Rocky Mountain Vending"
|
||||
width={220}
|
||||
height={55}
|
||||
className="h-12 w-auto object-contain lg:h-14"
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div className="flex min-w-0 flex-shrink-0 items-center gap-2 lg:gap-3">
|
||||
<CartButton
|
||||
onClick={() => dispatch({ type: "SET_CART", value: true })}
|
||||
/>
|
||||
<a
|
||||
href="tel:+14352339668"
|
||||
className="inline-flex min-h-10 items-center gap-2 rounded-full border border-border/60 bg-white px-3 text-sm font-medium text-foreground transition hover:border-primary/35 hover:text-primary lg:px-4"
|
||||
>
|
||||
<Phone className="h-4 w-4 flex-shrink-0" />
|
||||
<span className="hidden xl:inline">(435) 233-9668</span>
|
||||
<span className="xl:hidden">Call</span>
|
||||
</a>
|
||||
<Button
|
||||
onClick={() => dispatch({ type: "SET_MODAL", value: true })}
|
||||
className="h-10 whitespace-nowrap rounded-full bg-primary px-4 text-sm hover:bg-primary/90 lg:px-5"
|
||||
size="lg"
|
||||
>
|
||||
Get Free Machine
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[3.5rem] items-center justify-center border-t border-border/45 py-2">
|
||||
<nav className="flex flex-wrap items-center justify-center gap-x-1 gap-y-2 lg:gap-x-2">
|
||||
<Link href="/" className={desktopLinkClassName}>
|
||||
Home
|
||||
</Link>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className={cn(
|
||||
desktopLinkClassName,
|
||||
"gap-1.5 data-[state=open]:text-primary"
|
||||
)}
|
||||
>
|
||||
Who We Serve
|
||||
<ChevronDown className="h-4 w-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className={cn("w-56", desktopDropdownClassName)}
|
||||
>
|
||||
{whoWeServeItems.map((item) => (
|
||||
<DropdownMenuItem
|
||||
key={item.href}
|
||||
asChild
|
||||
className="rounded-xl px-3 py-2.5"
|
||||
>
|
||||
<Link href={item.href}>{item.label}</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className={cn(
|
||||
desktopLinkClassName,
|
||||
"gap-1.5 data-[state=open]:text-primary"
|
||||
)}
|
||||
>
|
||||
Vending Machines
|
||||
<ChevronDown className="h-4 w-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className={cn("w-64", desktopDropdownClassName)}
|
||||
>
|
||||
{vendingMachinesItems.map((item) => (
|
||||
<DropdownMenuItem
|
||||
key={item.href}
|
||||
asChild
|
||||
className="rounded-xl px-3 py-2.5"
|
||||
>
|
||||
<Link href={item.href}>{item.label}</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className={cn(
|
||||
desktopLinkClassName,
|
||||
"gap-1.5 data-[state=open]:text-primary"
|
||||
)}
|
||||
>
|
||||
Services
|
||||
<ChevronDown className="h-4 w-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className={cn("w-72", desktopDropdownClassName)}
|
||||
>
|
||||
{servicesItems.map((item) => (
|
||||
<DropdownMenuItem
|
||||
key={item.href}
|
||||
asChild
|
||||
className="rounded-xl px-3 py-2.5"
|
||||
>
|
||||
<Link href={item.href}>{item.label}</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<Link href="/contact-us" className={desktopLinkClassName}>
|
||||
Contact Us
|
||||
</Link>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className={cn(
|
||||
desktopLinkClassName,
|
||||
"gap-1.5 data-[state=open]:text-primary"
|
||||
)}
|
||||
>
|
||||
More
|
||||
<ChevronDown className="h-4 w-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className={cn("w-56", desktopDropdownClassName)}
|
||||
>
|
||||
{moreItems.map((item) => (
|
||||
<DropdownMenuItem
|
||||
key={item.href}
|
||||
asChild
|
||||
className="rounded-xl px-3 py-2.5"
|
||||
>
|
||||
<Link href={item.href}>{item.label}</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
{state.isMenuOpen && (
|
||||
<nav className="border-t border-border/40 py-5 md:hidden">
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export function PublicSurface({
|
|||
return (
|
||||
<Component
|
||||
className={cn(
|
||||
"rounded-[var(--public-surface-radius)] border border-border/70 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,251,243,0.96))] p-5 shadow-[var(--public-surface-shadow)] md:p-7",
|
||||
"rounded-[var(--public-surface-radius)] border border-border/65 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,249,240,0.96))] p-5 shadow-[0_20px_52px_rgba(15,23,42,0.075)] md:p-7",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -120,7 +120,7 @@ export function PublicInset({
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-[var(--public-inset-radius)] border border-border/60 bg-white/95 p-4 shadow-[0_10px_28px_rgba(15,23,42,0.06)]",
|
||||
"rounded-[var(--public-inset-radius)] border border-border/55 bg-[linear-gradient(180deg,rgba(255,255,255,0.94),rgba(255,250,244,0.92))] p-4 shadow-[0_12px_30px_rgba(15,23,42,0.055)]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -144,14 +144,16 @@ export function PublicSectionHeader({
|
|||
className,
|
||||
}: PublicSectionHeaderProps) {
|
||||
return (
|
||||
<div className={cn("space-y-2", className)}>
|
||||
<div className={cn("space-y-2.5", className)}>
|
||||
<p className="text-[0.72rem] font-semibold uppercase tracking-[0.22em] text-primary/80">
|
||||
{eyebrow}
|
||||
</p>
|
||||
<h2 className="text-xl font-semibold tracking-tight text-foreground md:text-[1.375rem]">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-sm leading-6 text-muted-foreground">{description}</p>
|
||||
<p className="max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function RequestMachineSection() {
|
|||
<section id="request-machine" className="bg-background py-16 md:py-24">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start">
|
||||
<PublicSurface className="bg-white p-6 md:p-8 lg:sticky lg:top-28">
|
||||
<PublicSurface className="p-6 md:p-8 lg:sticky lg:top-28">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-primary">
|
||||
<Package className="h-4 w-4" />
|
||||
Free Placement
|
||||
|
|
@ -61,7 +61,7 @@ export function RequestMachineSection() {
|
|||
</div>
|
||||
</PublicSurface>
|
||||
|
||||
<PublicSurface className="bg-white p-5 md:p-7">
|
||||
<PublicSurface className="p-5 md:p-7">
|
||||
<RequestMachineForm
|
||||
onSubmit={(data) =>
|
||||
console.log("Machine request form submitted:", data)
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export function ReviewsPage() {
|
|||
|
||||
<div className="mt-6">
|
||||
<iframe
|
||||
className="lc_reviews_widget min-h-[900px] w-full rounded-[1.5rem] border border-border/60 bg-background"
|
||||
className="lc_reviews_widget min-h-[780px] w-full rounded-[1.5rem] border border-border/60 bg-background"
|
||||
src="https://reputationhub.site/reputation/widgets/review_widget/YAoWLgNSid8oG44j9BjG"
|
||||
frameBorder="0"
|
||||
scrolling="no"
|
||||
|
|
@ -81,7 +81,7 @@ export function ReviewsPage() {
|
|||
</PublicSurface>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
|
||||
<section className="mt-12 grid gap-6 lg:grid-cols-[1.05fr_0.95fr]">
|
||||
<PublicSurface>
|
||||
<h2 className="text-3xl font-semibold tracking-tight text-balance">
|
||||
What businesses usually want to verify before they choose a vendor
|
||||
|
|
@ -120,21 +120,32 @@ export function ReviewsPage() {
|
|||
<PublicSurface className="flex flex-col justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Next Step
|
||||
Why It Matters
|
||||
</p>
|
||||
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
|
||||
Want to see whether your location qualifies?
|
||||
Reviews are usually the last confidence check before someone reaches out.
|
||||
</h2>
|
||||
<p className="mt-3 text-base leading-relaxed text-muted-foreground">
|
||||
Tell us about your traffic, breakroom, or customer area and
|
||||
we'll help you decide between free placement, machine sales,
|
||||
or service help.
|
||||
Most businesses are trying to verify the same things: follow-through,
|
||||
communication, and whether the machines stay stocked and working
|
||||
after install. If that sounds like your checklist too, we can help
|
||||
you sort through next steps quickly.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 grid gap-4 sm:grid-cols-2">
|
||||
<div className="mt-6 grid gap-4">
|
||||
<PublicInset className="p-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">
|
||||
Common Questions
|
||||
</p>
|
||||
<ul className="mt-3 space-y-3 text-sm leading-relaxed text-muted-foreground">
|
||||
<li>Does this location qualify for free placement?</li>
|
||||
<li>Can Rocky handle repairs and restocking without extra staff work on our side?</li>
|
||||
<li>Should we ask about placement, machine sales, or direct service help?</li>
|
||||
</ul>
|
||||
</PublicInset>
|
||||
<Link
|
||||
href="/#request-machine"
|
||||
className="rounded-[1.5rem] border border-border/60 bg-white p-5 text-left transition hover:border-primary/30 hover:text-primary"
|
||||
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
|
||||
|
|
@ -146,7 +157,7 @@ export function ReviewsPage() {
|
|||
</Link>
|
||||
<Link
|
||||
href="/contact-us#contact-form"
|
||||
className="rounded-[1.5rem] border border-border/60 bg-white p-5 text-left transition hover:border-primary/30 hover:text-primary"
|
||||
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
|
||||
|
|
@ -157,6 +168,16 @@ export function ReviewsPage() {
|
|||
</p>
|
||||
</Link>
|
||||
</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">
|
||||
<p className="text-sm font-semibold text-foreground">
|
||||
Looking for a direct answer?
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
||||
Tell us about your location, traffic, and what kind of help you
|
||||
need. We'll point you toward the right option instead of
|
||||
making you guess between service pages.
|
||||
</p>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { ReactNode } from "react"
|
|||
import { CheckCircle2 } from "lucide-react"
|
||||
import { DropdownPageShell } from "@/components/dropdown-page-shell"
|
||||
import { PublicInset } from "@/components/public-surface"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Link from "next/link"
|
||||
|
||||
interface WhoWeServePageProps {
|
||||
title: string
|
||||
|
|
@ -55,6 +57,51 @@ export function WhoWeServePage({
|
|||
description ||
|
||||
"See how Rocky Mountain Vending adapts machine placement, product mix, and ongoing service to the way this kind of location actually runs."
|
||||
}
|
||||
headerContent={
|
||||
<div className="flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||
<Button asChild size="lg" className="min-h-11 rounded-full px-6">
|
||||
<Link href="/contact-us#contact-form">Talk to Our Team</Link>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="min-h-11 rounded-full px-6"
|
||||
>
|
||||
<Link href="/#request-machine">See If You Qualify</Link>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
contentIntro={
|
||||
<>
|
||||
<PublicInset className="h-full p-5 md:p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
How We Tailor Service
|
||||
</p>
|
||||
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance text-foreground">
|
||||
We shape the setup around the pace of the location.
|
||||
</h2>
|
||||
<ul className="mt-4 space-y-3 text-sm leading-relaxed text-muted-foreground">
|
||||
<li>Machine type and product mix matched to how people actually use the space.</li>
|
||||
<li>Placement recommendations based on traffic flow, break patterns, and visibility.</li>
|
||||
<li>Service cadence adjusted so stocking and support stay consistent without adding staff work.</li>
|
||||
</ul>
|
||||
</PublicInset>
|
||||
<PublicInset className="h-full p-5 md:p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
|
||||
Good Fit Signals
|
||||
</p>
|
||||
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance text-foreground">
|
||||
These are usually the reasons businesses reach out first.
|
||||
</h2>
|
||||
<ul className="mt-4 space-y-3 text-sm leading-relaxed text-muted-foreground">
|
||||
<li>Your team or visitors need easier access to drinks, snacks, or convenience items on site.</li>
|
||||
<li>You want a cleaner vending setup without daily oversight falling back on your staff.</li>
|
||||
<li>You need local follow-through when a machine needs restocking, repair, or payment support.</li>
|
||||
</ul>
|
||||
</PublicInset>
|
||||
</>
|
||||
}
|
||||
content={
|
||||
<div className="text-foreground">{content}</div>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue