This commit is contained in:
2026-03-10 18:31:23 +01:00
parent 66225e4662
commit 4455605394
180 changed files with 9005 additions and 0 deletions

196
app/about/page.tsx Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

BIN
app/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

14
app/icon.svg Normal file
View 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

View 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
View 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>
);
}

View 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
View 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 &amp; 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 &amp; 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
View 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
View 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 &amp; 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 &amp; Gravel, Flagstone,
and Boulders &amp; 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 &amp; Gravel</span>
<span className="inventory-chip">Boulders &amp; 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
View 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
View 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,
}));
}