Template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F22Ms5Cxam5UHyxaCrtZzu
164 lines
6.2 KiB
TypeScript
164 lines
6.2 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
|
|
import { CollectionCard } from "@/components/CollectionCard";
|
|
import { ProductCard } from "@/components/ProductCard";
|
|
import { content } from "@/lib/content";
|
|
import { HOME_QUERY } from "@/lib/queries";
|
|
import { canonicalUrl } from "@/lib/site";
|
|
import { staticStorefrontClient } from "@/lib/storefront-static";
|
|
|
|
export const metadata: Metadata = {
|
|
title: "CORE — Discover our latest collection",
|
|
description: content.home.hero.subtitle,
|
|
alternates: { canonical: "/" },
|
|
openGraph: {
|
|
title: "CORE — Discover our latest collection",
|
|
description: content.home.hero.subtitle,
|
|
type: "website",
|
|
url: canonicalUrl("/"),
|
|
},
|
|
twitter: { card: "summary_large_image" },
|
|
};
|
|
|
|
/** Home catalog fetch. */
|
|
async function fetchHome() {
|
|
const { data, errors } = await staticStorefrontClient.graphql(HOME_QUERY);
|
|
if (errors) {
|
|
console.error("[hydrogen] Home query failed", errors);
|
|
}
|
|
return {
|
|
featuredProducts: data?.featuredProducts?.nodes ?? [],
|
|
featuredCollections: data?.featuredCollections?.nodes ?? [],
|
|
};
|
|
}
|
|
|
|
type HomeData = Awaited<ReturnType<typeof fetchHome>>;
|
|
type ProductNode = HomeData["featuredProducts"][number];
|
|
type CollectionNode = HomeData["featuredCollections"][number];
|
|
|
|
export default async function HomePage() {
|
|
const { featuredProducts, featuredCollections } = await fetchHome();
|
|
|
|
return (
|
|
<>
|
|
<Hero />
|
|
<BestSellers products={featuredProducts} />
|
|
<ShopByCategory collections={featuredCollections} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Hero() {
|
|
// The hero is the LCP image (notes/home.md): eager + high fetch priority.
|
|
// Unsplash is a third-party host, so it passes through `shopifyImageUrl`
|
|
// unchanged — build a width-descriptor srcset directly from its `w=` param
|
|
// and pair it with `sizes="100vw"` (F12). DPR `srcSetFor` is not used here:
|
|
// `sizes` is a no-op for 1x/2x descriptors.
|
|
const heroBaseUrl =
|
|
"https://images.unsplash.com/photo-1653398597732-37fc919284dd?auto=format&fit=crop&q=80";
|
|
const heroSrcSet = `${heroBaseUrl}&w=480 480w, ${heroBaseUrl}&w=1024 1024w, ${heroBaseUrl}&w=2000 2000w`;
|
|
|
|
return (
|
|
<section className="max-w-page px-margin mx-auto w-full">
|
|
<div
|
|
className="bleed-full min-h-hero relative overflow-hidden"
|
|
aria-labelledby="hero-heading"
|
|
>
|
|
<div className="bg-surface-secondary absolute inset-0">
|
|
<img
|
|
src={`${heroBaseUrl}&w=2000`}
|
|
srcSet={heroSrcSet}
|
|
sizes="100vw"
|
|
alt="A white chair beside a white wall"
|
|
className="h-full w-full object-cover"
|
|
loading="eager"
|
|
fetchPriority="high"
|
|
/>
|
|
</div>
|
|
<div className="overlay-dark pointer-events-none absolute inset-0" />
|
|
<div className="max-w-page px-margin text-interactive-text min-h-hero relative z-10 mx-auto flex flex-col items-start justify-end p-8 pb-12">
|
|
<h1 id="hero-heading" className="type-display mb-3 max-w-2xl">
|
|
{content.home.hero.heading}
|
|
</h1>
|
|
<p className="type-body-lg mb-6 max-w-prose opacity-90">{content.home.hero.subtitle}</p>
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<Link
|
|
href="/collections"
|
|
className="rounded-button button-primary focus-visible:outline-accent inline-flex h-11 items-center justify-center gap-2 px-5 text-sm font-medium no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
>
|
|
{content.home.hero.primaryCta}
|
|
</Link>
|
|
<Link
|
|
href="/collections"
|
|
className="rounded-button button-secondary focus-visible:outline-accent inline-flex h-11 items-center justify-center gap-2 px-5 text-sm font-medium no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
>
|
|
{content.home.hero.secondaryCta}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function BestSellers({ products }: { products: ProductNode[] }) {
|
|
return (
|
|
<section className="bg-surface w-full pt-20 pb-12">
|
|
<div className="max-w-page px-margin mx-auto mb-4 flex items-center justify-between">
|
|
<h2 className="type-heading-xl">{content.home.bestSellers}</h2>
|
|
<Link
|
|
href="/collections"
|
|
className="min-h-touch-target text-on-surface focus-visible:outline-accent inline-flex items-center gap-1 rounded-sm text-sm font-normal no-underline hover:opacity-70 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 motion-safe:transition-opacity"
|
|
>
|
|
<span>{content.home.viewAll}</span>
|
|
<span
|
|
className="inline-flex size-4 shrink-0 items-center justify-center"
|
|
aria-hidden="true"
|
|
>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="size-4"
|
|
>
|
|
<line x1="5" y1="12" x2="19" y2="12" />
|
|
<polyline points="12 5 19 12 12 19" />
|
|
</svg>
|
|
</span>
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="max-w-page px-margin mx-auto contain-paint">
|
|
<ul role="list" className="grid grid-cols-1 gap-x-1 gap-y-10 md:grid-cols-2 lg:grid-cols-4">
|
|
{products.map((product) => (
|
|
<li key={product.id}>
|
|
<ProductCard product={product} loading="lazy" />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function ShopByCategory({ collections }: { collections: CollectionNode[] }) {
|
|
return (
|
|
<section className="w-full py-12" aria-labelledby="category-heading">
|
|
<h2 id="category-heading" className="type-heading-xl max-w-page px-margin mx-auto mb-4">
|
|
{content.home.shopByCategory}
|
|
</h2>
|
|
<ul role="list" className="max-w-page px-margin mx-auto grid grid-cols-1 md:grid-cols-3">
|
|
{collections.map((collection) => (
|
|
<li key={collection.id}>
|
|
<CollectionCard collection={collection} loading="lazy" />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
);
|
|
}
|