Fertig
This commit is contained in:
196
app/about/page.tsx
Normal file
196
app/about/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import { aboutHighlights, buildStory, siteConfig } from "@/data/site-content";
|
||||
import { breadcrumbSchema, buildPageMetadata } from "@/lib/seo";
|
||||
import { MotionSection } from "@/components/motion-section";
|
||||
import { FadeUp, FadeIn, SlideIn } from "@/components/page-hero-motion";
|
||||
|
||||
export const metadata = buildPageMetadata({
|
||||
title: "About Southern Masonry Supply",
|
||||
description:
|
||||
"Learn how Southern Masonry Supply has served Corpus Christi with masonry and landscaping materials since 1990.",
|
||||
path: "/about",
|
||||
});
|
||||
|
||||
export default function AboutPage() {
|
||||
const breadcrumbs = [
|
||||
{ name: "Home", path: "/" },
|
||||
{ name: "About", path: "/about" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd id="about-breadcrumbs" data={breadcrumbSchema(breadcrumbs)} />
|
||||
|
||||
<div className="breadcrumb-strip">
|
||||
<div className="container">
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="page-hero">
|
||||
<div className="container page-hero-shell">
|
||||
<div className="page-hero-copy">
|
||||
<FadeUp delay={0.05}>
|
||||
<span className="eyebrow">Family owned and operated</span>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.15}>
|
||||
<h1 style={{ marginTop: "1.25rem", marginBottom: "1.75rem" }}>
|
||||
Serving Corpus Christi projects with material knowledge that lasts.
|
||||
</h1>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.25}>
|
||||
<p className="hero-copy" style={{ marginBottom: "2rem" }}>
|
||||
Southern Masonry Supply has spent more than 34 years helping
|
||||
contractors, homeowners, architects, and designers source the
|
||||
right masonry and landscaping materials for projects large and
|
||||
small.
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.35}>
|
||||
<div className="page-hero-meta">
|
||||
<span>Since 1990 in South Texas</span>
|
||||
<span>Family-owned: Sid Smith Jr.</span>
|
||||
<span>Project-grounded recommendations</span>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
<FadeIn delay={0.2} className="page-hero-visual">
|
||||
<Image
|
||||
src="/images/hero_about.webp"
|
||||
alt="Southern Masonry Supply heritage tools"
|
||||
fill
|
||||
priority
|
||||
sizes="(max-width: 1100px) 100vw, 40vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
<span className="hero-visual-note">
|
||||
Built for long-horizon projects
|
||||
</span>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section bg-layer overflow-hidden">
|
||||
<div className="container about-story">
|
||||
<div className="about-story-copy" style={{ gap: "4rem" }}>
|
||||
{buildStory.map((section, idx) => (
|
||||
<MotionSection key={section.title} delay={idx * 0.1} direction="up">
|
||||
<article className="story-block" style={{ paddingBottom: "1rem" }}>
|
||||
<span className="eyebrow">{section.eyebrow}</span>
|
||||
<h2 style={{ marginTop: "0.75rem", marginBottom: "1.25rem" }}>{section.title}</h2>
|
||||
<p style={{ lineHeight: 1.8 }}>{section.copy}</p>
|
||||
</article>
|
||||
</MotionSection>
|
||||
))}
|
||||
</div>
|
||||
<MotionSection direction="right" className="about-story-media">
|
||||
<div className="image-frame decorative">
|
||||
<Image
|
||||
src="/images/flagstone_pathway_garden_png_1773134755795.webp"
|
||||
alt="Flagstone pathway and landscaping project"
|
||||
fill
|
||||
sizes="(max-width: 960px) 100vw, 40vw"
|
||||
quality={70}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
</MotionSection>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section section-contrast">
|
||||
<div className="container">
|
||||
<MotionSection>
|
||||
<div className="section-header align-center" style={{ marginBottom: "4rem" }}>
|
||||
<span className="eyebrow">The Southern Standard</span>
|
||||
<h2 style={{ marginTop: "1rem" }}>Why builders trust our yard</h2>
|
||||
</div>
|
||||
</MotionSection>
|
||||
<div className="feature-grid">
|
||||
{aboutHighlights.map((item, idx) => (
|
||||
<MotionSection key={item.title} delay={idx * 0.08} direction="up">
|
||||
<article className="feature-card" style={{ padding: "2.5rem 2rem" }}>
|
||||
<div className="feature-icon" aria-hidden="true">
|
||||
{item.icon}
|
||||
</div>
|
||||
<h3 style={{ marginTop: "1.25rem", marginBottom: "0.75rem" }}>{item.title}</h3>
|
||||
<p style={{ lineHeight: 1.75 }}>{item.description}</p>
|
||||
</article>
|
||||
</MotionSection>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<div className="container content-panel glass-panel">
|
||||
<SlideIn direction="left" className="content-panel-media">
|
||||
<div className="image-frame wide elevated">
|
||||
<Image
|
||||
src="/images/delivery_truck_logistics_png_1773134721043.webp"
|
||||
alt="Delivery truck for masonry and landscaping orders"
|
||||
fill
|
||||
sizes="(max-width: 960px) 100vw, 45vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
</SlideIn>
|
||||
<SlideIn direction="right" delay={0.15} className="content-panel-copy">
|
||||
<span className="eyebrow">Service that stays practical</span>
|
||||
<h2 className="text-display" style={{ marginTop: "1rem", marginBottom: "1.5rem" }}>
|
||||
Our mission is simple: make good material easier to source.
|
||||
</h2>
|
||||
<p className="text-lg" style={{ marginBottom: "1.5rem", lineHeight: 1.8 }}>
|
||||
Founded in 1990 and led by Sid Smith Jr., Southern Masonry Supply
|
||||
stays focused on responsive service, reliable stock levels, and
|
||||
materials worth putting into long-term work. Sid's hands-on
|
||||
expertise in both masonry and landscaping ensures the yard remains
|
||||
the southern standard for quality and practical guidance.
|
||||
</p>
|
||||
<p style={{ lineHeight: 1.8, marginBottom: "2rem" }}>
|
||||
Whether you are ordering flagstone by the ton, pebbles by the
|
||||
bag, or masonry cement for a new project phase, our team keeps the
|
||||
conversation grounded in application, quantity, and timing.
|
||||
</p>
|
||||
<div className="inline-actions">
|
||||
<Link href="/masonry-supplies" className="button button-primary">
|
||||
View masonry supplies
|
||||
</Link>
|
||||
<Link
|
||||
href="/landscaping-supplies"
|
||||
className="button button-secondary invert"
|
||||
>
|
||||
View landscaping supplies
|
||||
</Link>
|
||||
</div>
|
||||
</SlideIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MotionSection>
|
||||
<section className="section section-tight">
|
||||
<div className="container cta-panel">
|
||||
<div>
|
||||
<span className="eyebrow">Visit or call</span>
|
||||
<h2 style={{ marginTop: "0.75rem", marginBottom: "1rem" }}>{siteConfig.address.street}</h2>
|
||||
<p style={{ lineHeight: 1.7 }}>
|
||||
Stop by the yard during business hours or call{" "}
|
||||
{siteConfig.phoneDisplay} for material availability and delivery
|
||||
planning.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/contact" className="button button-primary">
|
||||
Contact us
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</MotionSection>
|
||||
</>
|
||||
);
|
||||
}
|
||||
65
app/api/contact/route.ts
Normal file
65
app/api/contact/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
type ContactPayload = {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
projectType?: string;
|
||||
materialInterest?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
function isValidEmail(value: string) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
}
|
||||
|
||||
function isValidPhone(value: string) {
|
||||
return /^[0-9()+.\-\s]{10,}$/.test(value);
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const payload = (await request.json()) as ContactPayload;
|
||||
const fieldErrors: Record<string, string> = {};
|
||||
|
||||
const name = payload.name?.trim() ?? "";
|
||||
const phone = payload.phone?.trim() ?? "";
|
||||
const email = payload.email?.trim() ?? "";
|
||||
const message = payload.message?.trim() ?? "";
|
||||
|
||||
if (!name) {
|
||||
fieldErrors.name = "Please enter your full name.";
|
||||
}
|
||||
|
||||
if (!phone) {
|
||||
fieldErrors.phone = "Please enter a phone number.";
|
||||
} else if (!isValidPhone(phone)) {
|
||||
fieldErrors.phone = "Please enter a valid phone number.";
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
fieldErrors.email = "Please enter an email address.";
|
||||
} else if (!isValidEmail(email)) {
|
||||
fieldErrors.email = "Please enter a valid email address.";
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
fieldErrors.message = "Please describe your project or material request.";
|
||||
}
|
||||
|
||||
if (Object.keys(fieldErrors).length > 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Please correct the highlighted fields and try again.",
|
||||
fieldErrors,
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message:
|
||||
"Thanks for reaching out. This form is wired to a placeholder API route and ready for email delivery integration.",
|
||||
});
|
||||
}
|
||||
155
app/contact/page.tsx
Normal file
155
app/contact/page.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs";
|
||||
import { ContactForm } from "@/components/contact-form";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import { deliveryHighlights, siteConfig } from "@/data/site-content";
|
||||
import {
|
||||
breadcrumbSchema,
|
||||
buildPageMetadata,
|
||||
} from "@/lib/seo";
|
||||
import { FadeUp, FadeIn } from "@/components/page-hero-motion";
|
||||
import { MotionSection } from "@/components/motion-section";
|
||||
|
||||
export const metadata = buildPageMetadata({
|
||||
title: "Contact Southern Masonry Supply in Corpus Christi, TX",
|
||||
description:
|
||||
"Reach Southern Masonry Supply at 5205 Agnes St, Corpus Christi, TX 78405 or call (361) 289-1074 during business hours.",
|
||||
path: "/contact",
|
||||
image: "/images/delivery_truck_logistics_png_1773134721043.png",
|
||||
});
|
||||
|
||||
export default function ContactPage() {
|
||||
const breadcrumbs = [
|
||||
{ name: "Home", path: "/" },
|
||||
{ name: "Contact", path: "/contact" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd id="contact-breadcrumbs" data={breadcrumbSchema(breadcrumbs)} />
|
||||
|
||||
<div className="breadcrumb-strip">
|
||||
<div className="container">
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="page-hero">
|
||||
<div className="container page-hero-shell">
|
||||
<div className="page-hero-copy">
|
||||
<FadeUp delay={0.05}>
|
||||
<span className="eyebrow">Contact and delivery</span>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.15}>
|
||||
<h1 style={{ marginTop: "1.25rem", marginBottom: "1.75rem" }}>
|
||||
Reach the yard, request a quote, or line up a delivery.
|
||||
</h1>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.25}>
|
||||
<p className="hero-copy" style={{ marginBottom: "2rem" }}>
|
||||
Share the material, quantity, and timing you need. We will get
|
||||
back with the right next step.
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.35}>
|
||||
<div className="page-hero-meta">
|
||||
<span>Quotes during business hours</span>
|
||||
<span>Delivery thresholds made clear upfront</span>
|
||||
<span>Material-first project guidance</span>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
<FadeIn delay={0.2} className="page-hero-visual">
|
||||
<Image
|
||||
src="/images/delivery_truck_logistics_png_1773134721043.webp"
|
||||
alt="Southern Masonry Supply delivery service"
|
||||
fill
|
||||
priority
|
||||
sizes="(max-width: 1100px) 100vw, 40vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
<span className="hero-visual-note">
|
||||
Call first for stock and routing
|
||||
</span>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<div className="container contact-layout">
|
||||
<div className="contact-card">
|
||||
<div className="card-heading">
|
||||
<span className="eyebrow">Send us a message</span>
|
||||
<h2>Tell us about your project.</h2>
|
||||
</div>
|
||||
<Suspense fallback={<p>Loading contact form...</p>}>
|
||||
<ContactForm />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<div className="contact-sidebar">
|
||||
<div className="map-card" style={{ padding: 0, overflow: "hidden", background: "transparent", border: "none" }}>
|
||||
<div className="contact-map-embed">
|
||||
<iframe
|
||||
src="https://www.google.com/maps?q=5205+Agnes+St,+Corpus+Christi,+TX+78405&output=embed"
|
||||
title="Southern Masonry Supply location"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
aria-label="Map showing Southern Masonry Supply at 5205 Agnes St, Corpus Christi TX"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
href={siteConfig.mapUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="button button-primary"
|
||||
style={{ width: "100%", marginTop: "0", borderRadius: "0 0 12px 12px" }}
|
||||
>
|
||||
Open in Google Maps
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="details-grid">
|
||||
<article className="detail-card">
|
||||
<h3>Visit the yard</h3>
|
||||
<p>{siteConfig.address.street}</p>
|
||||
<p>{siteConfig.address.cityStateZip}</p>
|
||||
<a href={siteConfig.phoneHref} className="text-link">
|
||||
{siteConfig.phoneDisplay}
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article className="detail-card">
|
||||
<h3>Hours</h3>
|
||||
<ul className="detail-list">
|
||||
{siteConfig.hours.map((item) => (
|
||||
<li key={item.label}>
|
||||
<span>{item.label}</span>
|
||||
<strong>{item.value}</strong>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="detail-card detail-card-wide">
|
||||
<h3>Delivery notes</h3>
|
||||
<ul className="detail-list stacked">
|
||||
{deliveryHighlights.map((item) => (
|
||||
<li key={item.title}>
|
||||
<strong>{item.title}</strong>
|
||||
<span>{item.description}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
3149
app/globals.css
Normal file
3149
app/globals.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/icon.png
Normal file
BIN
app/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
14
app/icon.svg
Normal file
14
app/icon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" rx="14" fill="#0f8ea1" />
|
||||
<rect x="10" y="45" width="44" height="5" rx="2.5" fill="#f47f20" />
|
||||
<text
|
||||
x="32"
|
||||
y="36"
|
||||
font-family="Arial Rounded MT Bold, Arial, sans-serif"
|
||||
font-weight="700"
|
||||
font-size="24"
|
||||
fill="#ffffff"
|
||||
text-anchor="middle"
|
||||
letter-spacing="0.5"
|
||||
>SM</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
121
app/landscaping-supplies/page.tsx
Normal file
121
app/landscaping-supplies/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import NextImage from "next/image";
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import { MaterialCatalog } from "@/components/material-catalog";
|
||||
import {
|
||||
landscapingCategory,
|
||||
siteConfig,
|
||||
} from "@/data/site-content";
|
||||
import { breadcrumbSchema, buildPageMetadata, itemListSchema } from "@/lib/seo";
|
||||
import { landscapingMaterials } from "@/data/site-content";
|
||||
import { FadeUp, FadeIn } from "@/components/page-hero-motion";
|
||||
|
||||
export const metadata = buildPageMetadata({
|
||||
title: "Landscaping Supplies in Corpus Christi, TX",
|
||||
description:
|
||||
"Browse flagstone, sand & gravel, boulders & stone, and Mexico Beach Pebbles from Southern Masonry Supply in Corpus Christi, TX.",
|
||||
path: "/landscaping-supplies",
|
||||
image: "/images/hero_landscaping.png",
|
||||
});
|
||||
|
||||
export default function LandscapingSuppliesPage() {
|
||||
const breadcrumbs = [
|
||||
{ name: "Home", path: "/" },
|
||||
{ name: "Landscaping Supplies", path: "/landscaping-supplies" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
id="landscaping-breadcrumbs"
|
||||
data={breadcrumbSchema(breadcrumbs)}
|
||||
/>
|
||||
<JsonLd
|
||||
id="landscaping-item-list"
|
||||
data={itemListSchema({
|
||||
name: "Landscaping Supplies",
|
||||
path: "/landscaping-supplies",
|
||||
items: [
|
||||
{ name: "Mexico Beach Pebbles" },
|
||||
{ name: '1" - 2" Mexico Beach Pebbles Multicolor' },
|
||||
{ name: "Mexico Beach Pebbles 40 lb. Bags" },
|
||||
{ name: "Mexico Beach Pebbles 2 Ton Basket" },
|
||||
{ name: "Sand & Gravel" },
|
||||
{ name: "Flagstone" },
|
||||
{ name: '1" Oklahoma' },
|
||||
{ name: '1" Oklahoma Silver Mist' },
|
||||
{ name: '1" Arkansas Chestnut' },
|
||||
{ name: '1/2" Arkansas Blue' },
|
||||
{ name: '1" Arkansas Blue' },
|
||||
{ name: '2" Arkansas Blue' },
|
||||
{ name: '1/2" Arkansas Brown' },
|
||||
{ name: '1" Arkansas Brown' },
|
||||
{ name: '2" Arkansas Brown' },
|
||||
{ name: '1-1/2" Mexico White' },
|
||||
{ name: '1-1/2" Mexico Creama' },
|
||||
{ name: '1" Mexico Rosa Flagstone' },
|
||||
{ name: '1-1/2" Mexico Rosa Flagstone' },
|
||||
{ name: '1-1/2" Mexico Gray Flagstone' },
|
||||
{ name: '1-1/2" Mexico Cafe Flagstone' },
|
||||
{ name: "Boulders & Stone" },
|
||||
],
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="breadcrumb-strip">
|
||||
<div className="container">
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="page-hero">
|
||||
<div className="container page-hero-shell">
|
||||
<div className="page-hero-copy">
|
||||
<FadeUp delay={0.05}>
|
||||
<span className="eyebrow">Landscaping supplies</span>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.15}>
|
||||
<h1 style={{ marginTop: "1.25rem", marginBottom: "1.75rem" }}>
|
||||
{landscapingCategory.title}
|
||||
</h1>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.25}>
|
||||
<p className="hero-copy" style={{ marginBottom: "2rem" }}>
|
||||
{landscapingCategory.description}
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.35}>
|
||||
<div className="page-hero-meta">
|
||||
<span>{siteConfig.cityRegion}</span>
|
||||
<span>Bulk bags and by-the-yard options</span>
|
||||
<span>Delivery available</span>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
<FadeIn delay={0.2} className="page-hero-visual">
|
||||
<NextImage
|
||||
src="/images/hero_landscaping.webp"
|
||||
alt="Southern Masonry Supply landscaping gravel and rocks"
|
||||
fill
|
||||
priority
|
||||
sizes="(max-width: 1100px) 100vw, 40vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
<span className="hero-visual-note">
|
||||
Flagstone, pebbles, aggregates, and boulders
|
||||
</span>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MaterialCatalog
|
||||
heroImage={landscapingCategory.heroImage}
|
||||
materials={landscapingMaterials}
|
||||
intro={landscapingCategory.intro}
|
||||
deliveryNote={landscapingCategory.deliveryNote}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
73
app/layout.tsx
Normal file
73
app/layout.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { SiteHeader } from "@/components/site-header";
|
||||
import { ScrollReveal } from "@/components/scroll-reveal";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import {
|
||||
buildMetadataBase,
|
||||
businessSchema,
|
||||
websiteSchema,
|
||||
} from "@/lib/seo";
|
||||
import { siteConfig } from "@/data/site-content";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: buildMetadataBase(),
|
||||
title: {
|
||||
default: `${siteConfig.name} | Masonry & Landscaping Supplies`,
|
||||
template: `%s | ${siteConfig.name}`,
|
||||
},
|
||||
description: siteConfig.description,
|
||||
alternates: {
|
||||
canonical: "/",
|
||||
},
|
||||
openGraph: {
|
||||
type: "website",
|
||||
title: `${siteConfig.name} | Masonry & Landscaping Supplies`,
|
||||
description: siteConfig.description,
|
||||
siteName: siteConfig.name,
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.defaultOgImage,
|
||||
alt: siteConfig.name,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: `${siteConfig.name} | Masonry & Landscaping Supplies`,
|
||||
description: siteConfig.description,
|
||||
images: [siteConfig.defaultOgImage],
|
||||
},
|
||||
icons: {
|
||||
icon: "/icon.png",
|
||||
shortcut: "/icon.png",
|
||||
apple: "/icon.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<ScrollReveal />
|
||||
<JsonLd
|
||||
id="global-schema"
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [websiteSchema(), businessSchema()],
|
||||
}}
|
||||
/>
|
||||
<div className="site-shell">
|
||||
<SiteHeader />
|
||||
<main>{children}</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
101
app/masonry-supplies/page.tsx
Normal file
101
app/masonry-supplies/page.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import NextImage from "next/image";
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import { MaterialCatalog } from "@/components/material-catalog";
|
||||
import {
|
||||
masonryCategory,
|
||||
siteConfig,
|
||||
} from "@/data/site-content";
|
||||
import { breadcrumbSchema, buildPageMetadata, itemListSchema } from "@/lib/seo";
|
||||
import { masonryMaterials } from "@/data/site-content";
|
||||
import { FadeUp, FadeIn } from "@/components/page-hero-motion";
|
||||
|
||||
export const metadata = buildPageMetadata({
|
||||
title: "Masonry Supplies in Corpus Christi, TX",
|
||||
description:
|
||||
"Browse masonry tools and bagged cement from Southern Masonry Supply in Corpus Christi, TX.",
|
||||
path: "/masonry-supplies",
|
||||
image: "/images/hero_masonry.png",
|
||||
});
|
||||
|
||||
export default function MasonrySuppliesPage() {
|
||||
const breadcrumbs = [
|
||||
{ name: "Home", path: "/" },
|
||||
{ name: "Masonry Supplies", path: "/masonry-supplies" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd id="masonry-breadcrumbs" data={breadcrumbSchema(breadcrumbs)} />
|
||||
<JsonLd
|
||||
id="masonry-item-list"
|
||||
data={itemListSchema({
|
||||
name: "Masonry Supplies",
|
||||
path: "/masonry-supplies",
|
||||
items: [
|
||||
{ name: "Masonry Tools" },
|
||||
{ name: "Bagged Cement" },
|
||||
{ name: "Alamo Type N Masonry Cement" },
|
||||
{ name: "Alamo White Portland Cement" },
|
||||
{ name: "Alamo White Masonry Cement" },
|
||||
{ name: "Alamo Portland Cement Type IL" },
|
||||
{ name: "Alamo Masonry Cement Type N" },
|
||||
],
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="breadcrumb-strip">
|
||||
<div className="container">
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="page-hero">
|
||||
<div className="container page-hero-shell">
|
||||
<div className="page-hero-copy">
|
||||
<FadeUp delay={0.05}>
|
||||
<span className="eyebrow">Masonry supplies</span>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.15}>
|
||||
<h1 style={{ marginTop: "1.25rem", marginBottom: "1.75rem" }}>{masonryCategory.title}</h1>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.25}>
|
||||
<p className="hero-copy" style={{ marginBottom: "2rem" }}>
|
||||
{masonryCategory.description}
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.35}>
|
||||
<div className="page-hero-meta">
|
||||
<span>{siteConfig.cityRegion}</span>
|
||||
<span>Quote-first service</span>
|
||||
<span>Delivery available</span>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
<FadeIn delay={0.2} className="page-hero-visual">
|
||||
<NextImage
|
||||
src="/images/hero_masonry.webp"
|
||||
alt="Southern Masonry Supply tools and cement"
|
||||
fill
|
||||
priority
|
||||
sizes="(max-width: 1100px) 100vw, 40vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
<span className="hero-visual-note">
|
||||
Tools, cement, and fast follow-up quoting
|
||||
</span>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MaterialCatalog
|
||||
heroImage={masonryCategory.heroImage}
|
||||
materials={masonryMaterials}
|
||||
intro={masonryCategory.intro}
|
||||
deliveryNote={masonryCategory.deliveryNote}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
62
app/not-found.tsx
Normal file
62
app/not-found.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="nf-hero">
|
||||
{/* subtle brick grid overlay */}
|
||||
<div className="nf-bg-grid" aria-hidden="true" />
|
||||
|
||||
{/* radial glow behind 404 */}
|
||||
<div className="nf-glow" aria-hidden="true" />
|
||||
|
||||
<div className="nf-inner">
|
||||
<p className="nf-eyebrow">ERROR</p>
|
||||
|
||||
<h1 className="nf-number" aria-label="404">404</h1>
|
||||
|
||||
<h2 className="nf-heading">
|
||||
That page is not part of<br />the current yard layout.
|
||||
</h2>
|
||||
|
||||
<p className="nf-sub">
|
||||
Head back to the homepage, check our material catalogs,
|
||||
or contact the yard — we'll find what you're looking for.
|
||||
</p>
|
||||
|
||||
<div className="nf-actions">
|
||||
<Link href="/" className="button button-primary">
|
||||
← Back home
|
||||
</Link>
|
||||
<Link href="/contact" className="nf-btn-ghost">
|
||||
Contact the yard
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="nf-cards">
|
||||
<Link href="/masonry-supplies#catalog" className="nf-card">
|
||||
<span className="nf-card-label">Masonry Catalog</span>
|
||||
<strong className="nf-card-title">
|
||||
Brick, mortar, cement & tools
|
||||
</strong>
|
||||
<span className="nf-card-cta">
|
||||
Open masonry inventory
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12h14M12 5l7 7-7 7" /></svg>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<Link href="/landscaping-supplies#catalog" className="nf-card">
|
||||
<span className="nf-card-label">Natural Stone</span>
|
||||
<strong className="nf-card-title">
|
||||
Flagstone, gravel & decorative rock
|
||||
</strong>
|
||||
<span className="nf-card-cta">
|
||||
Browse landscaping materials
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12h14M12 5l7 7-7 7" /></svg>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
491
app/page.tsx
Normal file
491
app/page.tsx
Normal file
@@ -0,0 +1,491 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { siteConfig, processSteps, faqs, featuredMaterials, googleReviews, homeStats } from "@/data/site-content";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import { ProcessTimeline } from "@/components/process-timeline";
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs";
|
||||
import { buildPageMetadata, breadcrumbSchema, faqPageSchema } from "@/lib/seo";
|
||||
import { TestimonialsCarousel } from "@/components/testimonials-carousel";
|
||||
import { HomeCTASection } from "@/components/home-cta-section";
|
||||
import { MotionSection } from "@/components/motion-section";
|
||||
import { HeroCinema } from "@/components/hero-cinema";
|
||||
import { CountUpStat } from "@/components/count-up-stat";
|
||||
|
||||
export const metadata = buildPageMetadata({
|
||||
title: "South Texas's Most Trusted Masonry Supply",
|
||||
description:
|
||||
"Providing premium brick, stone, and landscaping materials to Corpus Christi's contractors and homeowners since 1990.",
|
||||
path: "/",
|
||||
});
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<JsonLd
|
||||
id="home-breadcrumbs"
|
||||
data={breadcrumbSchema([{ name: "Home", path: "/" }])}
|
||||
/>
|
||||
<JsonLd id="home-faq-schema" data={faqPageSchema(faqs)} />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="home-hero">
|
||||
<div className="home-hero-left">
|
||||
<div className="home-hero-copy reveal">
|
||||
<span className="eyebrow" style={{ color: "var(--primary)" }}>
|
||||
SINCE 1990
|
||||
</span>
|
||||
<h1>South Texas's Most Trusted Masonry Supply</h1>
|
||||
<p>
|
||||
Providing premium brick, stone, and landscaping materials to
|
||||
Corpus Christi's contractors and homeowners with dependable
|
||||
on-site delivery.
|
||||
</p>
|
||||
|
||||
<div className="hero-reviews">
|
||||
<div className="stars">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<svg
|
||||
key={i}
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
|
||||
</svg>
|
||||
))}
|
||||
</div>
|
||||
<span>4.9 Stars (14 Google Reviews)</span>
|
||||
</div>
|
||||
|
||||
<div className="hero-actions">
|
||||
<Link href="/contact" className="button button-primary">
|
||||
GET A FREE QUOTE
|
||||
</Link>
|
||||
<Link
|
||||
href="/products"
|
||||
className="button button-outline"
|
||||
style={{
|
||||
borderColor: "white",
|
||||
color: "white",
|
||||
marginLeft: "1rem",
|
||||
}}
|
||||
>
|
||||
VIEW INVENTORY
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="home-hero-visual" aria-hidden="true">
|
||||
<HeroCinema />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick Service Band */}
|
||||
<div className="quick-service-band">
|
||||
<div className="container">
|
||||
<div className="quick-service-grid">
|
||||
<div className="service-item">
|
||||
<h4>Phone</h4>
|
||||
<span>{siteConfig.phoneDisplay}</span>
|
||||
</div>
|
||||
<div className="service-item">
|
||||
<h4>Address</h4>
|
||||
<span>{siteConfig.address.street}</span>
|
||||
</div>
|
||||
<div className="service-item">
|
||||
<h4>Hours</h4>
|
||||
<span>Mon - Fri 8 AM - 5 PM</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Band */}
|
||||
<section className="stats-band">
|
||||
<div className="container">
|
||||
<div className="stats-grid">
|
||||
{homeStats.map((stat, i) => (
|
||||
<MotionSection key={stat.label} delay={i * 0.08} direction="up">
|
||||
<CountUpStat value={stat.value} label={stat.label} />
|
||||
</MotionSection>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Product Categories */}
|
||||
<section className="section bg-soft">
|
||||
<div className="container">
|
||||
<div
|
||||
className="section-header reveal"
|
||||
style={{ textAlign: "center", marginBottom: "4rem" }}
|
||||
>
|
||||
<span className="eyebrow">OUR PRODUCTS</span>
|
||||
<h2>Premium Materials for Any Project</h2>
|
||||
<p style={{ maxWidth: "600px", margin: "1rem auto" }}>
|
||||
From foundation to finish, we carry the materials you need for
|
||||
professional masonry and landscaping results.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="category-grid"
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
gap: "2rem",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="category-card reveal reveal-delay-1"
|
||||
style={{
|
||||
background: "white",
|
||||
borderRadius: "12px",
|
||||
overflow: "hidden",
|
||||
boxShadow: "var(--shadow)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "240px",
|
||||
background: "#f1f3f5",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src="/images/hero_masonry_landscaping_png_1773134515262.webp"
|
||||
alt="Masonry Supplies"
|
||||
fill
|
||||
sizes="(max-width: 900px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
quality={70}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "2rem" }}>
|
||||
<h3 style={{ marginBottom: "1rem" }}>Masonry Supplies</h3>
|
||||
<p>
|
||||
Brick, concrete blocks, mortar, and lintels for structural and
|
||||
aesthetic projects.
|
||||
</p>
|
||||
<Link
|
||||
href="/masonry-supplies#catalog"
|
||||
style={{ color: "var(--primary)", fontWeight: "700" }}
|
||||
>
|
||||
LEARN MORE →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="category-card reveal reveal-delay-2"
|
||||
style={{
|
||||
background: "white",
|
||||
borderRadius: "12px",
|
||||
overflow: "hidden",
|
||||
boxShadow: "var(--shadow)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "240px",
|
||||
background: "#f1f3f5",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src="/images/flagstone_stack_premium_png_1773134568102.webp"
|
||||
alt="Natural Stone"
|
||||
fill
|
||||
sizes="(max-width: 900px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
quality={70}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "2rem" }}>
|
||||
<h3 style={{ marginBottom: "1rem" }}>Natural Stone</h3>
|
||||
<p>
|
||||
Flagstone, limestone, and decorative rock to elevate your
|
||||
landscape and architecture.
|
||||
</p>
|
||||
<Link
|
||||
href="/landscaping-supplies#catalog"
|
||||
style={{ color: "var(--primary)", fontWeight: "700" }}
|
||||
>
|
||||
LEARN MORE →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="category-card reveal reveal-delay-3"
|
||||
style={{
|
||||
background: "white",
|
||||
borderRadius: "12px",
|
||||
overflow: "hidden",
|
||||
boxShadow: "var(--shadow)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "240px",
|
||||
background: "#f1f3f5",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src="/images/masonry_tools_display_png_1773134531475.webp"
|
||||
alt="Tools & Materials"
|
||||
fill
|
||||
sizes="(max-width: 900px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
quality={70}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "2rem" }}>
|
||||
<h3 style={{ marginBottom: "1rem" }}>Tools & Materials</h3>
|
||||
<p>
|
||||
High-quality masonry tools, expansion joints, and sealers to
|
||||
get the job done right.
|
||||
</p>
|
||||
<Link
|
||||
href="/masonry-supplies#catalog"
|
||||
style={{ color: "var(--primary)", fontWeight: "700" }}
|
||||
>
|
||||
LEARN MORE →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Products */}
|
||||
<section className="section reveal">
|
||||
<div className="container">
|
||||
<div className="featured-header">
|
||||
<div>
|
||||
<span className="eyebrow">IN STOCK NOW</span>
|
||||
<h2>Featured Masonry Products</h2>
|
||||
</div>
|
||||
<Link href="/products" className="button button-outline">
|
||||
VIEW FULL INVENTORY
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="featured-grid">
|
||||
{featuredMaterials.slice(0, 4).map((product, i) => (
|
||||
<div
|
||||
key={product.slug}
|
||||
className={`reveal reveal-delay-${(i % 3) + 1}`}
|
||||
style={{
|
||||
background: "white",
|
||||
borderRadius: "8px",
|
||||
overflow: "hidden",
|
||||
border: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "200px",
|
||||
overflow: "hidden",
|
||||
background: "#f1f3f5",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 25vw"
|
||||
quality={68}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "1.5rem" }}>
|
||||
<h4 style={{ fontSize: "1.125rem", marginBottom: "0.5rem" }}>
|
||||
{product.name}
|
||||
</h4>
|
||||
<p style={{ fontSize: "0.875rem", marginBottom: "0" }}>
|
||||
Available for delivery
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Why Choose Us */}
|
||||
<section className="section bg-soft">
|
||||
<div className="container">
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gap: "5rem",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<span className="eyebrow">THE SMS DIFFERENCE</span>
|
||||
<h2>Professional Supply, Personal Service</h2>
|
||||
<p style={{ fontSize: "1.125rem", marginTop: "1.5rem" }}>
|
||||
We aren't just a yard; we're your project partner. With over
|
||||
34 years of experience serving South Texas, we know our
|
||||
materials and we know our customers.
|
||||
</p>
|
||||
<ul
|
||||
style={{
|
||||
marginTop: "2rem",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gap: "2rem",
|
||||
}}
|
||||
>
|
||||
<li>
|
||||
<strong
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "1.25rem",
|
||||
color: "var(--primary)",
|
||||
}}
|
||||
>
|
||||
34+ YEARS
|
||||
</strong>
|
||||
Experience in the Corpus Christi area.
|
||||
</li>
|
||||
<li>
|
||||
<strong
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "1.25rem",
|
||||
color: "var(--primary)",
|
||||
}}
|
||||
>
|
||||
RELIABLE DELIVERY
|
||||
</strong>
|
||||
Dependable site drops when you need them.
|
||||
</li>
|
||||
<li>
|
||||
<strong
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "1.25rem",
|
||||
color: "var(--primary)",
|
||||
}}
|
||||
>
|
||||
OPEN TO PUBLIC
|
||||
</strong>
|
||||
Serving both contractors and homeowners.
|
||||
</li>
|
||||
<li>
|
||||
<strong
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "1.25rem",
|
||||
color: "var(--primary)",
|
||||
}}
|
||||
>
|
||||
LOCAL EXPERTISE
|
||||
</strong>
|
||||
Knowledgeable staff for all project types.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style={{ position: "relative" }}>
|
||||
<Image
|
||||
src="/images/delivery_truck_logistics_png_1773134721043.webp"
|
||||
alt="Southern Masonry Supply Delivery Truck"
|
||||
width={640}
|
||||
height={640}
|
||||
sizes="(max-width: 1024px) 100vw, 50vw"
|
||||
quality={72}
|
||||
style={{
|
||||
borderRadius: "12px",
|
||||
boxShadow: "var(--shadow-lg)",
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
<div className="hero-visual-note">RELIABLE ON-SITE DELIVERY</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Process Section */}
|
||||
<section className="section reveal">
|
||||
<div className="container">
|
||||
<div
|
||||
className="section-header"
|
||||
style={{ textAlign: "center", marginBottom: "4rem" }}
|
||||
>
|
||||
<span className="eyebrow">OUR PROCESS</span>
|
||||
<h2>How to Get Your Materials</h2>
|
||||
</div>
|
||||
<ProcessTimeline steps={processSteps} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Testimonials */}
|
||||
<section className="section bg-soft">
|
||||
<div className="container">
|
||||
<MotionSection>
|
||||
<div className="section-header" style={{ textAlign: "center", marginBottom: "3rem" }}>
|
||||
<span className="eyebrow">WHAT CUSTOMERS SAY</span>
|
||||
<h2>Trusted by Corpus Christi</h2>
|
||||
</div>
|
||||
</MotionSection>
|
||||
</div>
|
||||
<TestimonialsCarousel reviews={googleReviews} />
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="section reveal">
|
||||
<div className="container">
|
||||
<div
|
||||
className="section-header"
|
||||
style={{ textAlign: "center", marginBottom: "4rem" }}
|
||||
>
|
||||
<span className="eyebrow">COMMON QUESTIONS</span>
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: "800px",
|
||||
margin: "0 auto",
|
||||
display: "grid",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
{faqs.map((faq) => (
|
||||
<details
|
||||
key={faq.question}
|
||||
className="faq-item"
|
||||
style={{
|
||||
background: "white",
|
||||
padding: "1.5rem",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<summary
|
||||
style={{
|
||||
fontWeight: "700",
|
||||
cursor: "pointer",
|
||||
listStyle: "none",
|
||||
}}
|
||||
>
|
||||
{faq.question}
|
||||
</summary>
|
||||
<p style={{ marginTop: "1rem", color: "var(--text-muted)" }}>
|
||||
{faq.answer}
|
||||
</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Call to Action with inline form */}
|
||||
<HomeCTASection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
169
app/products/page.tsx
Normal file
169
app/products/page.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Breadcrumbs } from "@/components/breadcrumbs";
|
||||
import { JsonLd } from "@/components/json-ld";
|
||||
import { breadcrumbSchema, buildPageMetadata, itemListSchema } from "@/lib/seo";
|
||||
|
||||
export const metadata = buildPageMetadata({
|
||||
title: "Masonry & Landscaping Supplies in Corpus Christi, TX",
|
||||
description:
|
||||
"Browse masonry supplies and landscaping supplies from Southern Masonry Supply in Corpus Christi, TX.",
|
||||
path: "/products",
|
||||
image: "/images/hero_main.png",
|
||||
});
|
||||
|
||||
type ProductsPageProps = {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
};
|
||||
|
||||
function firstValue(value: string | string[] | undefined) {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
|
||||
export default async function ProductsPage({
|
||||
searchParams,
|
||||
}: ProductsPageProps) {
|
||||
const params = await searchParams;
|
||||
const category = firstValue(params.category)?.toLowerCase();
|
||||
|
||||
if (category === "masonry" || category === "tools") {
|
||||
redirect("/masonry-supplies#catalog");
|
||||
}
|
||||
|
||||
if (
|
||||
category === "stone" ||
|
||||
category === "natural-stone" ||
|
||||
category === "landscaping"
|
||||
) {
|
||||
redirect("/landscaping-supplies#catalog");
|
||||
}
|
||||
|
||||
const breadcrumbs = [
|
||||
{ name: "Home", path: "/" },
|
||||
{ name: "Products", path: "/products" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd id="products-breadcrumbs" data={breadcrumbSchema(breadcrumbs)} />
|
||||
<JsonLd
|
||||
id="products-item-list"
|
||||
data={itemListSchema({
|
||||
name: "Masonry & Landscaping Supplies",
|
||||
path: "/products",
|
||||
items: [
|
||||
{ name: "Masonry Supplies", path: "/masonry-supplies" },
|
||||
{ name: "Landscaping Supplies", path: "/landscaping-supplies" },
|
||||
{ name: "Contact Us", path: "/contact" },
|
||||
],
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="breadcrumb-strip">
|
||||
<div className="container">
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="section bg-soft">
|
||||
<div className="container">
|
||||
<div className="inventory-overview-header">
|
||||
<span className="eyebrow">Masonry & Landscaping Supplies</span>
|
||||
<h1>Masonry & Landscaping Supplies</h1>
|
||||
<p>
|
||||
Southern Masonry Supply offers masonry supplies and landscaping
|
||||
supplies in Corpus Christi, TX and surrounding cities. Browse the
|
||||
catalog that matches your project and get in touch for delivery
|
||||
information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="inventory-overview-grid">
|
||||
<article className="inventory-overview-card">
|
||||
<div className="inventory-overview-media">
|
||||
<Image
|
||||
src="/images/hero_masonry.webp"
|
||||
alt="Masonry supplies and tools"
|
||||
fill
|
||||
sizes="(max-width: 900px) 100vw, 50vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
<div className="inventory-overview-copy">
|
||||
<span className="not-found-card-label">Masonry supplies</span>
|
||||
<h2>Masonry tools and bagged cement.</h2>
|
||||
<p>
|
||||
Browse masonry tools and bagged cement from Southern Masonry
|
||||
Supply.
|
||||
</p>
|
||||
<div
|
||||
className="inventory-chip-list"
|
||||
aria-label="Masonry highlights"
|
||||
>
|
||||
<span className="inventory-chip">Masonry Tools</span>
|
||||
<span className="inventory-chip">Bagged Cement</span>
|
||||
<span className="inventory-chip">Get in Touch</span>
|
||||
</div>
|
||||
<Link
|
||||
href="/masonry-supplies#catalog"
|
||||
className="button button-primary"
|
||||
>
|
||||
Open masonry catalog
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article className="inventory-overview-card">
|
||||
<div className="inventory-overview-media">
|
||||
<Image
|
||||
src="/images/hero_landscaping.webp"
|
||||
alt="Natural stone and landscaping materials"
|
||||
fill
|
||||
sizes="(max-width: 900px) 100vw, 50vw"
|
||||
quality={72}
|
||||
className="cover-image"
|
||||
/>
|
||||
</div>
|
||||
<div className="inventory-overview-copy">
|
||||
<span className="not-found-card-label">Natural stone</span>
|
||||
<h2>Flagstone, gravel, pebbles, and boulders.</h2>
|
||||
<p>
|
||||
Browse Mexico Beach Pebbles, Sand & Gravel, Flagstone,
|
||||
and Boulders & Stone from Southern Masonry Supply.
|
||||
</p>
|
||||
<div
|
||||
className="inventory-chip-list"
|
||||
aria-label="Landscaping highlights"
|
||||
>
|
||||
<span className="inventory-chip">Mexico Beach Pebbles</span>
|
||||
<span className="inventory-chip">Flagstone</span>
|
||||
<span className="inventory-chip">Sand & Gravel</span>
|
||||
<span className="inventory-chip">Boulders & Stone</span>
|
||||
</div>
|
||||
<Link
|
||||
href="/landscaping-supplies#catalog"
|
||||
className="button button-secondary"
|
||||
>
|
||||
Open landscaping catalog
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div className="inventory-overview-header" style={{ marginTop: "3rem" }}>
|
||||
<p>
|
||||
Delivery is also available and quoted at time of purchase. For
|
||||
flagstone there is a minimum of one ton, and for landscaping
|
||||
aggregates there is a 3 yard minimum.
|
||||
</p>
|
||||
<Link href="/contact" className="button button-outline">
|
||||
Contact us
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
12
app/robots.ts
Normal file
12
app/robots.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { buildAbsoluteUrl } from "@/lib/seo";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
},
|
||||
sitemap: buildAbsoluteUrl("/sitemap.xml"),
|
||||
};
|
||||
}
|
||||
20
app/sitemap.ts
Normal file
20
app/sitemap.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { buildAbsoluteUrl } from "@/lib/seo";
|
||||
|
||||
const routes = [
|
||||
"/",
|
||||
"/about",
|
||||
"/products",
|
||||
"/masonry-supplies",
|
||||
"/landscaping-supplies",
|
||||
"/contact",
|
||||
];
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return routes.map((route) => ({
|
||||
url: buildAbsoluteUrl(route),
|
||||
lastModified: new Date(),
|
||||
changeFrequency: route === "/" ? "weekly" : "monthly",
|
||||
priority: route === "/" ? 1 : 0.8,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user